/*
 * Copyright (C) 2010 Teleal GmbH, Switzerland
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.teleal.lemma.reader.content.filter;

import org.teleal.common.xhtml.Option;
import org.teleal.lemma.anchor.CitationAnchor;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Excludes and includes lines, depending on the given fragment rules of the citation anchor.
 *
 * @author Christian Bauer
 */
public class FragmentFilter implements ContentFilter {

    final private Logger log = Logger.getLogger(FragmentFilter.class.getName());

    final private Pattern fragmentLabelPattern;

    public FragmentFilter(Pattern fragmentLabelPattern) {
        this.fragmentLabelPattern = fragmentLabelPattern;
    }

    public Pattern getFragmentLabelPattern() {
        return fragmentLabelPattern;
    }

    public String[] filter(String[] source, CitationAnchor citation) {

        if (source == null || source.length == 0) return source;

        Option includeOption = citation.getOption(CitationAnchor.OptionKey.INCLUDE);
        Option excludeOption = citation.getOption(CitationAnchor.OptionKey.EXCLUDE);
        String[] includeFragments = includeOption != null ? includeOption.getValues() : new String[0];
        String[] excludeFragments = excludeOption != null ? excludeOption.getValues() : new String[0];

        log.fine("Filtering " + source.length + " source lines, included/excluded fragments: "
                + includeFragments.length + "/" + excludeFragments.length);

        List<Integer> includedLines = new ArrayList();

        if (includeFragments.length == 0) {
            // Include ALL
            for (int i = 0; i < source.length; i++) {
                includedLines.add(i);
            }
        } else {
            // Include fragment blocks
            includedLines.addAll(getFragmentLines(source, includeFragments));
        }

        // Exclude fragment blocks
        List<Integer> excludedLines = getFragmentLines(source, excludeFragments);

        List<String> filtered = new ArrayList();

        for (int i = 0; i < source.length; i++) {
            String s = source[i];
            if (includedLines.contains(i) && !excludedLines.contains(i)) {
                filtered.add(s);
            }
        }
        return filtered.toArray(new String[filtered.size()]);
    }

    protected List<Integer> getFragmentLines(String[] source, String[] fragments) {
        List<Integer> lines = new ArrayList();

        // Include only lines that are inside a block that BEGINs/ENDs with a fragment label; as
        // a special case, non-closed blocks are also supported for the most common case when
        // only a single line is actually marked.

        for (String fragment : fragments) {

            List<Integer> fragmentLines = new ArrayList();
            boolean inBlock = false;
            int lastBegin = 0;

            for (int line = 0; line < source.length; line++) {

                String s = source[line];
                if (isLineMatchingFragment(s, fragment)) {
                    if (!inBlock) {
                        // BEGIN the block demarcated by the fragment label
                        inBlock = true;
                        lastBegin = line;
                    } else {
                        // END the block demarcated by the fragment label
                        inBlock = false;
                        // Add last line of block
                        fragmentLines.add(line);
                    }
                }

                // If we are inside a BEGIN/END fragment block, add the line
                if (inBlock) {
                    fragmentLines.add(line);
                }

                // If the last block was not ENDed and we have reached the end, roll back to the
                // last BEGIN but include that line. In other words: Enable single-line blocks
                if (line == source.length - 1 && inBlock) {
                    Iterator<Integer> it = fragmentLines.iterator();
                    while (it.hasNext()) {
                        if (it.next() > lastBegin) it.remove();
                    }
                }
            }
            lines.addAll(fragmentLines);
        }
        return lines;
    }

    protected boolean isLineMatchingFragment(String line, String fragment) {
        Matcher m = getFragmentLabelPattern().matcher(line);
        return m.matches() && m.group(2).equals(fragment);
    }

}
