package com.wizzardo.http;

import com.wizzardo.http.request.Request;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author: wizzardo
 * Date: 25.09.14
 */
public class UrlMapping<T> {

    private static Pattern VARIABLES = Pattern.compile("\\$\\{?([a-zA-Z_]+[\\w]*)\\}?");

    protected Map<String, UrlMapping<T>> mapping = new HashMap<>();
    protected Map<String, UrlMappingMatcher<T>> regexpMapping = new LinkedHashMap<>();
    protected T value;
    protected UrlMapping<T> parent;

    public UrlMapping() {
    }

    protected UrlMapping(UrlMapping parent) {
        this.parent = parent;
    }

    protected boolean checkNextPart() {
        return true;
    }

    private static abstract class UrlMappingMatcher<T> extends UrlMapping<T> {
        protected UrlMappingMatcher(UrlMapping parent) {
            super(parent);
        }

        protected abstract boolean matches(String urlPart);
    }

    private static class UrlMappingMatcherAny<T> extends UrlMappingMatcher<T> {
        protected UrlMappingMatcherAny(UrlMapping parent) {
            super(parent);
        }

        @Override
        protected boolean matches(String urlPart) {
            return true;
        }

        @Override
        protected boolean checkNextPart() {
            return false;
        }
    }

    private static class UrlMappingMatcherPattern<T> extends UrlMappingMatcher<T> {
        Pattern pattern;

        protected UrlMappingMatcherPattern(UrlMapping parent, String pattern) {
            super(parent);
            this.pattern = Pattern.compile(pattern);
        }

        @Override
        protected boolean matches(String urlPart) {
            return pattern.matcher(urlPart).matches();
        }

        @Override
        protected boolean checkNextPart() {
            return false;
        }
    }

    private static class UrlMappingWithVariables<T> extends UrlMappingMatcher<T> {
        String[] variables;
        Pattern pattern;
        int partNumber;

        UrlMappingWithVariables(UrlMapping parent, String part, int partNumber) {
            super(parent);
            this.partNumber = partNumber;
            Matcher m = VARIABLES.matcher(part);
            final List<String> vars = new ArrayList<>();
            while (m.find()) {
                vars.add(m.group(1));
            }
            part = part.replaceAll(VARIABLES.pattern(), "([^/]+)").replace("/([^/]+)?", "/?([^/]+)?");

            pattern = Pattern.compile(part);
            variables = vars.toArray(new String[vars.size()]);
        }

        @Override
        protected void prepare(Request request) {
            super.prepare(request);
            String part = request.path().getPart(partNumber);
            if (part == null)
                return;
            Matcher matcher = pattern.matcher(part);
            if (matcher.find()) {
                for (int i = 1; i <= variables.length; i++) {
                    request.param(variables[i - 1], matcher.group(i));
                }
            }
        }

        @Override
        protected boolean matches(String urlPart) {
            return pattern.matcher(urlPart).matches();
        }
    }

    protected void prepare(Request request) {
        if (parent != null)
            parent.prepare(request);
    }

    public T get(Request request) {
        UrlMapping<T> last = find(request.path());
        if (last != null && last.value != null)
            last.prepare(request);

        return last == null ? null : last.value;
    }

    private UrlMapping<T> find(Path path) {
        return find(path.parts());
    }

    public UrlMapping find(List<String> parts) {
        UrlMapping tree = this;
        for (int i = 0; i < parts.size() && tree != null; i++) {
            String part = parts.get(i);
            if (part.isEmpty())
                continue;

            tree = tree.find(part);
            if (tree != null && !tree.checkNextPart())
                break;
        }
        while (tree != null && tree.value == null)
            tree = tree.find("");

        return tree;
    }

    private UrlMapping find(String part) {
        UrlMapping handler = mapping.get(part);
        if (handler != null)
            return handler;

        for (Map.Entry<String, UrlMappingMatcher<T>> entry : regexpMapping.entrySet()) {
            if (entry.getValue().matches(part))
                return entry.getValue();
        }
        return null;
    }

    public UrlMapping append(String url, T handler) {
        String[] parts = url.split("/");
        UrlMapping<T> tree = this;
        int counter = 0;
        for (int i = 0; i < parts.length; i++) {
            String part = parts[i];
            if (part.isEmpty())
                continue;

            UrlMapping<T> next;
            if (part.contains("*")) {
                String pattern = part.replace("*", ".*");
                next = tree.regexpMapping.get(pattern);
                if (next == null) {
                    UrlMappingMatcher<T> t;
                    if (pattern.equals(".*"))
                        t = new UrlMappingMatcherAny<>(tree);
                    else
                        t = new UrlMappingMatcherPattern<>(tree, pattern);
                    tree.regexpMapping.put(pattern, t);
                    next = t;
                }
            } else if (part.contains("$")) {
                String pattern = convertRegexpVariables(part);
                next = tree.regexpMapping.get(pattern);
                if (next == null) {
                    UrlMappingWithVariables<T> t = new UrlMappingWithVariables<>(tree, part, counter);
                    tree.regexpMapping.put(pattern, t);
                    next = t;
                }
            } else {
                next = tree.mapping.get(part);
                if (next == null) {
                    next = new UrlMapping<>(tree);
                    tree.mapping.put(part, next);
                }
            }

            tree = next;
            counter++;
        }
        tree.value = handler;

        return this;
    }

    private String convertRegexpVariables(String s) {
        return s.replaceAll(VARIABLES.pattern(), "([^/]+)").replace("/([^/]+)?", "/?([^/]+)?");
    }
}

@@ -245,16 +245,22 @@
  static 
+final 
 Pattern 
@@ -316,24 +316,79 @@
 %5Cw%5D*)%5C%5C%7D?%22);
+%0A    private static final String OPTIONAL = %22(%5B%5E/%5D+)?%22;
 %0A%0A    protec
@@ -1390,32 +1390,199 @@
 false;%0A        %7D
+%0A%0A        @Override%0A        protected void setValue(T t) %7B%0A            super.setValue(t);%0A            if (parent != null)%0A                parent.setValue(t);%0A        %7D
 %0A    %7D%0A%0A    priv
@@ -3428,32 +3428,237 @@
 hes();%0A        %7D
+%0A%0A        @Override%0A        protected void setValue(T t) %7B%0A            super.setValue(t);%0A            if (parent != null && pattern.pattern().equals(OPTIONAL))%0A                parent.setValue(t);%0A        %7D
 %0A    %7D%0A%0A    prot
@@ -4452,93 +4452,8 @@
    %7D
-%0A        while (tree != null && tree.value == null)%0A            tree = tree.find(%22%22);
 %0A%0A  
@@ -6438,23 +6438,25 @@
 ree.
-v
+setV
 alue
- = 
+(
 handler
+)
 ;%0A%0A 
@@ -6474,32 +6474,93 @@
 rn this;%0A    %7D%0A%0A
+    protected void setValue(T t) %7B%0A        value = t;%0A    %7D%0A%0A
     private Stri

improve UrlMapping