package org.zwobble.mammoth.styles;

import com.google.common.collect.ImmutableList;
import org.parboiled.BaseParser;
import org.parboiled.Parboiled;
import org.parboiled.Rule;
import org.parboiled.parserunners.ReportingParseRunner;
import org.parboiled.support.ParsingResult;
import org.parboiled.support.Var;

import java.util.List;

import static org.zwobble.mammoth.util.MammothLists.list;
import static org.zwobble.mammoth.util.MammothMaps.map;

public class StyleMapParser extends BaseParser<Object> {
    public static HtmlPath parseHtmlPath(String value) {
        StyleMapParser parser = Parboiled.createParser(StyleMapParser.class);
        ReportingParseRunner runner = new ReportingParseRunner(parser.HtmlPath());
        ParsingResult<?> result = runner.run(value);
        if (result.hasErrors()) {
            // TODO: wrap in result
            throw new RuntimeException("Parse error");
        } else {
            return (HtmlPath) result.valueStack.peek();
        }
    }

    Rule HtmlPath() {
        ListVar<HtmlPathElement> elements = new ListVar<>();
        return Sequence(HtmlPathElements(elements), EOI, push(new HtmlPath(elements.build())));
    }

    Rule HtmlPathElements(ListVar<HtmlPathElement> elements) {
        Var<HtmlPathElement> element = new Var<>();
        return FirstOf(
            Sequence(
                HtmlPathElement(element),
                elements.append(element),
                ZeroOrMore(Sequence(Whitespace(), Ch('>'), Whitespace(), HtmlPathElement(element), elements.append(element)))),
            toRule(push(ImmutableList.builder())));
    }

    Rule HtmlPathElement(Var<HtmlPathElement> element) {
        ListVar<String> tagNames = new ListVar<>();
        ListVar<String> classNames = new ListVar<>();
        Var<Boolean> fresh = new Var<>();
        return Sequence(
            TagNames(tagNames),
            ClassNames(classNames),
            Fresh(fresh),
            element.set(new HtmlPathElement(
                tagNames.build(),
                classNames.isEmpty() ? map() : map("class", String.join(" ", classNames.build())),
                !fresh.get())));
    }

    Rule TagNames(ListVar<String> tagNames) {
        return Sequence(
            Identifier(),
            tagNames.append(match()),
            ZeroOrMore(Sequence("|", Identifier(), tagNames.append(match()))));
    }

    Rule ClassNames(ListVar<String> classNames) {
        Var<String> className = new Var<>();
        return ZeroOrMore(Sequence(ClassName(className), classNames.append(className)));
    }

    Rule ClassName(Var<String> className) {
        return Sequence(".", Identifier(), className.set(match()));
    }

    Rule Fresh(Var<Boolean> fresh) {
        return Sequence(fresh.set(false), Optional(":fresh", fresh.set(true)));
    }

    Rule Identifier() {
        return Sequence(
            FirstOf(CharRange('a', 'z'), CharRange('A', 'Z')),
            ZeroOrMore(FirstOf(CharRange('a', 'z'), CharRange('A', 'Z'), CharRange('0', '9'), Ch('-'), Ch('_'))));
    }

    Rule Whitespace() {
        return ZeroOrMore(AnyOf(" \t"));
    }

    class ListVar<T> extends Var<ImmutableList.Builder<T>> {
        boolean append(Var<T> element) {
            return append(element.get());
        }

        boolean append(T element) {
            if (get() == null) {
                set(ImmutableList.builder());
            }
            return set(get().add(element));
        }

        public List<T> build() {
            if (get() == null) {
                return list();
            } else {
                return get().build();
            }
        }

        public boolean isEmpty() {
            return get() == null;
        }
    }
}

@@ -495,14 +495,16 @@
 ser%3C
-Object
+HtmlPath
 %3E %7B%0A
@@ -666,16 +666,26 @@
 seRunner
+%3CHtmlPath%3E
  runner 
@@ -710,16 +710,18 @@
 seRunner
+%3C%3E
 (parser.
@@ -755,17 +755,24 @@
 gResult%3C
-?
+HtmlPath
 %3E result
@@ -957,19 +957,8 @@
 turn
- (HtmlPath)
  res
@@ -1104,16 +1104,25 @@
 equence(
+Optional(
 HtmlPath
@@ -1139,16 +1139,17 @@
 lements)
+)
 , EOI, p
@@ -1327,29 +1327,8 @@
 urn 
-FirstOf(%0A            
 Sequ
@@ -1345,20 +1345,16 @@
         
- 
-   
 HtmlPath
@@ -1363,36 +1363,32 @@
 ement(element),%0A
-    
             elem
@@ -1401,36 +1401,32 @@
 ppend(element),%0A
-    
             Zero
@@ -1535,60 +1535,8 @@
 ))))
-,%0A            toRule(push(ImmutableList.builder())))
 ;%0A  

Set type parameter of BaseParser to remove cast