package com.googlecode.yatspec.rendering.html.tagindex;

import com.googlecode.funclate.Model;
import com.googlecode.funclate.stringtemplate.EnhancedStringTemplateGroup;
import com.googlecode.totallylazy.Callable1;
import com.googlecode.totallylazy.Group;
import com.googlecode.totallylazy.Pair;
import com.googlecode.totallylazy.Sequence;
import com.googlecode.yatspec.junit.SpecResultListener;
import com.googlecode.yatspec.rendering.Index;
import com.googlecode.yatspec.rendering.html.HtmlResultRenderer;
import com.googlecode.yatspec.state.Result;
import com.googlecode.yatspec.state.Results;
import com.googlecode.yatspec.state.TestMethod;
import org.antlr.stringtemplate.StringTemplate;

import java.io.File;

import static com.googlecode.funclate.Model.mutable.model;
import static com.googlecode.totallylazy.Callables.first;
import static com.googlecode.totallylazy.Sequences.repeat;
import static com.googlecode.totallylazy.Sequences.sequence;
import static com.googlecode.yatspec.parsing.Files.overwrite;
import static com.googlecode.yatspec.rendering.html.HtmlResultRenderer.getCssMap;
import static com.googlecode.yatspec.rendering.html.HtmlResultRenderer.testMethodRelativePath;

public class HtmlTagIndexRenderer implements SpecResultListener {
    public static final String TAG_NAME = "tag";
    private final static Index index = new Index();
    private final TagFinder tagFinder;

    public HtmlTagIndexRenderer() {
        this(new NotesTagFinder());
    }

    public HtmlTagIndexRenderer(TagFinder tagFinder) {
        this.tagFinder = tagFinder;
    }

    @Override
    public void complete(File yatspecOutputDir, Result result) throws Exception {
        index.add(result);
        overwrite(outputFile(yatspecOutputDir), render(index));
    }

    public String render(Index index) throws Exception {
        EnhancedStringTemplateGroup group = new EnhancedStringTemplateGroup(getClass());
        StringTemplate template = group.getInstanceOf("index",
                model().
                        add("script", null).
                        add("stylesheet", HtmlResultRenderer.loadContent("yatspec.css")).
                        add("cssClass", getCssMap()).
                        add("tags", tagModels(index).toList()).
                        toMap());
        return template.toString();
    }

    private Sequence<Model> tagModels(Index index) {
        return index.
                entries().
                flatMap(testMethods()).
                flatMap(methodTags()).
                groupBy(first(String.class)).
                sortBy(groupKey(String.class)).
                map(toTagModel());
    }

    private Callable1<? super TestMethod, ? extends Iterable<Pair<String, TestMethod>>> methodTags() {
        return new Callable1<TestMethod, Iterable<Pair<String, TestMethod>>>() {
            @Override
            public Iterable<Pair<String, TestMethod>> call(TestMethod resultFileAndTestMethod) throws Exception {
                return sequence(tagFinder.tags(resultFileAndTestMethod)).zip(repeat(resultFileAndTestMethod));
            }
        };
    }

    private static <K> Callable1<Group<K, ?>, K> groupKey(Class<K> keyType) {
        return new Callable1<Group<K, ?>, K>() {
            @Override
            public K call(Group<K, ?> group) throws Exception {
                return group.key();
            }
        };
    }

    private static Callable1<Pair<String, TestMethod>, Model> tagModel() {
        return new Callable1<Pair<String, TestMethod>, Model>() {
            @Override
            public Model call(Pair<String, TestMethod> tagAndTestMethod) throws Exception {
                TestMethod testMethod = tagAndTestMethod.second();
                return model().
                        add(TAG_NAME, tagAndTestMethod.first()).
                        add("package", testMethod.getPackageName()).
                        add("resultName", testMethod.getName()).
                        add("url", testMethodRelativePath(testMethod)).
                        add("class", getCssMap().get(testMethod.getStatus())).
                        add("name", testMethod.getDisplayName());
            }
        };
    }

    private static Callable1<? super Result, ? extends Iterable<TestMethod>> testMethods() {
        return new Callable1<Result, Iterable<TestMethod>>() {
            @Override
            public Iterable<TestMethod> call(Result fileResult) throws Exception {
                return sequence(fileResult).flatMap(Results.testMethods());
            }
        };
    }

    private static Callable1<Group<String, Pair<String, TestMethod>>, Model> toTagModel() {
        return new Callable1<Group<String, Pair<String, TestMethod>>, Model>() {
            @Override
            public Model call(Group<String, Pair<String, TestMethod>> tagGroup) throws Exception {
                return model().
                        add("name", tagGroup.key()).
                        add("results", tagGroup.map(tagModel()).toList());
            }
        };
    }

    private static File outputFile(File outputDirectory) {
        return new File(outputDirectory, "tag-index.html");
    }
}