/*
 * Copyright 2014 Hannes Janetzek
 * Copyright 2016-2017 devemux86
 *
 * This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
 * 
 * This program is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.oscim.theme.rule;

import org.oscim.theme.IRenderTheme.ThemeException;
import org.oscim.theme.rule.Rule.Element;
import org.oscim.theme.rule.Rule.NegativeRule;
import org.oscim.theme.rule.Rule.PositiveRuleK;
import org.oscim.theme.rule.Rule.PositiveRuleKV;
import org.oscim.theme.rule.Rule.PositiveRuleMultiKV;
import org.oscim.theme.rule.Rule.PositiveRuleV;
import org.oscim.theme.styles.RenderStyle;
import org.oscim.theme.styles.RenderStyle.StyleBuilder;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class RuleBuilder {

    private static final String[] EMPTY_KV = {};

    public enum RuleType {
        POSITIVE,
        NEGATIVE,
        EXCLUDE
    }

    public String cat;
    int zoom;
    int element;
    int selector;
    RuleType type;

    String keys[];
    String values[];

    ArrayList<RenderStyle> renderStyles = new ArrayList<>(4);
    ArrayList<RuleBuilder> subRules = new ArrayList<>(4);
    StyleBuilder<?>[] styleBuilder;

    private static final String STRING_NEGATION = "~";
    private static final String STRING_EXCLUSIVE = "-";
    private static final String SEPARATOR = "\\|";

    //private static final String STRING_WILDCARD = "*";

    public RuleBuilder(RuleType type, int element, int zoom, int selector,
                       String[] keys, String[] values) {
        this.type = type;
        this.element = element;
        this.zoom = zoom;
        this.selector = selector;
        this.keys = keys;
        this.values = values;
    }

    public RuleBuilder(RuleType type, String[] keys, String[] values) {
        this.element = Element.ANY;
        this.zoom = ~0;
        this.type = type;
        this.keys = keys;
        this.values = values;
    }

    public RuleBuilder() {
        this.type = RuleType.POSITIVE;
        this.element = Element.ANY;
        this.zoom = ~0;
        this.keys = EMPTY_KV;
        this.values = EMPTY_KV;
    }

    public static RuleBuilder create(String k, String v) {
        String[] keys = EMPTY_KV;
        String[] values = EMPTY_KV;
        RuleType type = RuleType.POSITIVE;

        if (v != null) {
            String[] split = v.split(SEPARATOR);
            List<String> valueList = new ArrayList<>(Arrays.asList(split));
            if (valueList.remove(STRING_NEGATION)) {
                type = RuleType.NEGATIVE;
                values = valueList.toArray(new String[valueList.size()]);
            } else if (valueList.remove(STRING_EXCLUSIVE)) {
                type = RuleType.EXCLUDE;
                values = valueList.toArray(new String[valueList.size()]);
            } else {
                values = split;
            }
        }

        if (k != null) {
            keys = k.split(SEPARATOR);
        }

        if (type != RuleType.POSITIVE) {
            if (keys == null || keys.length == 0)
                throw new ThemeException("negative rule requires key");
        }

        return new RuleBuilder(type, keys, values);
    }

    public RuleBuilder zoom(byte zoomMin, byte zoomMax) {
        // zoom-level bitmask
        zoom = 0;
        for (int z = zoomMin; z <= zoomMax && z < 32; z++)
            zoom |= (1 << z);

        return this;
    }

    public Rule onComplete(int[] level) {

        RenderStyle[] styles = null;
        Rule[] rules = null;

        if (styleBuilder != null)
            for (StyleBuilder<?> style : styleBuilder) {
                renderStyles.add(style.level(level[0]).build());
                level[0] += 2;
            }

        if (renderStyles.size() > 0) {
            styles = new RenderStyle[renderStyles.size()];
            renderStyles.toArray(styles);
        }

        if (subRules.size() > 0) {
            rules = new Rule[subRules.size()];
            for (int i = 0; i < rules.length; i++)
                rules[i] = subRules.get(i).onComplete(level);
        }

        int numKeys = keys.length;
        int numVals = values.length;

        if (numKeys == 0 && numVals == 0)
            return new Rule(element, zoom, selector, rules, styles)
                    .setCat(cat);

        for (int i = 0; i < numVals; i++)
            values[i] = values[i].intern();

        for (int i = 0; i < numKeys; i++)
            keys[i] = keys[i].intern();

        if (type != RuleType.POSITIVE)
            return new NegativeRule(type, element, zoom, selector, keys, values, rules, styles)
                    .setCat(cat);

        if (numKeys == 1 && numVals == 0)
            return new PositiveRuleK(element, zoom, selector, keys[0], rules, styles)
                    .setCat(cat);

        if (numKeys == 0 && numVals == 1)
            return new PositiveRuleV(element, zoom, selector, values[0], rules, styles)
                    .setCat(cat);

        if (numKeys == 1 && numVals == 1)
            return new PositiveRuleKV(element, zoom, selector, keys[0], values[0], rules, styles)
                    .setCat(cat);

        return new PositiveRuleMultiKV(element, zoom, selector, keys, values, rules, styles)
                .setCat(cat);
    }

    public RuleBuilder addStyle(RenderStyle style) {
        renderStyles.add(style);
        return this;
    }

    public RuleBuilder addSubRule(RuleBuilder rule) {
        subRules.add(rule);
        return this;
    }

    public RuleBuilder style(StyleBuilder<?>... styles) {
        styleBuilder = styles;
        return this;
    }

    public RuleBuilder rules(RuleBuilder... rules) {
        Collections.addAll(subRules, rules);
        return this;
    }

    public RuleBuilder select(int selector) {
        this.selector = selector;
        return this;
    }

    public RuleBuilder zoom(int zoom) {
        this.zoom = zoom;
        return this;
    }

    public RuleBuilder element(int element) {
        this.element = element;
        return this;
    }

    public RuleBuilder cat(String cat) {
        this.cat = cat;
        return this;
    }
}
