package ru.yandex.autodoc.common.doc.view;

import org.apache.commons.lang3.tuple.Pair;
import ru.yandex.autodoc.common.doc.view.format.JsonValue;
import ru.yandex.autodoc.common.doc.view.format.XmlValue;
import ru.yandex.autodoc.common.out.json.JsonValueWriter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author avhaliullin
 */
public interface Markup {
    default Group concat(Markup that) {
        return new Group(Arrays.asList(this, that));
    }

    default Group concat(String that) {
        return concat(new Text(that));
    }

    default Group concat(List<Markup> that) {
        List<Markup> items = new ArrayList<>(that.size() + 1);
        items.add(this);
        items.addAll(that);
        return new Group(items);
    }

    interface NonLeafMarkup extends Markup {
        List<Markup> getChildren();

        NonLeafMarkup mapContents(Function<Markup, Markup> map);
    }

    interface ContainerMarkup extends NonLeafMarkup {
        Markup getContent();

        ContainerMarkup replaceContent(Markup newContent);

        @Override
        default ContainerMarkup mapContents(Function<Markup, Markup> map) {
            Markup newContent = map.apply(getContent());
            return replaceContent(newContent);
        }

        @Override
        default List<Markup> getChildren() {
            return Collections.singletonList(getContent());
        }
    }

    abstract class DefaultContainerMarkup implements ContainerMarkup {
        private final Markup content;

        public DefaultContainerMarkup(Markup content) {
            this.content = content;
        }

        @Override
        public Markup getContent() {
            return content;
        }
    }

    interface CollectionMarkup extends NonLeafMarkup {
        List<Markup> getItems();

        @Override
        default List<Markup> getChildren() {
            return getItems();
        }

        CollectionMarkup replaceItems(List<Markup> newItems);

        @Override
        default CollectionMarkup mapContents(Function<Markup, Markup> map) {
            return replaceItems(
                    getItems().stream().map(map).collect(Collectors.toList())
            );
        }
    }

    class Text implements Markup {
        private final String content;

        public Text(String content) {
            this.content = content;
        }

        public String getContent() {
            return content;
        }
    }

    class PreformattedText implements Markup {
        private final String content;

        public PreformattedText(String content) {
            this.content = content;
        }

        public String getContent() {
            return content;
        }
    }

    class Group implements CollectionMarkup {
        private final List<Markup> items;

        public Group(List<Markup> items) {
            this.items = items;
        }

        @Override
        public List<Markup> getItems() {
            return items;
        }

        @Override
        public Group replaceItems(List<Markup> newItems) {
            return new Group(newItems);
        }

        @Override
        public Group concat(Markup that) {
            if (that instanceof Group) {
                Group thatGroup = (Group) that;
                List<Markup> newItems = new ArrayList<>(items.size() + thatGroup.items.size());
                newItems.addAll(this.items);
                newItems.addAll(thatGroup.getItems());
                return new Group(newItems);
            } else {
                List<Markup> newItems = new ArrayList<>(items.size() + 1);
                newItems.addAll(items);
                newItems.add(that);
                return new Group(newItems);
            }
        }
    }

    static Group group(Markup... elements) {
        return new Group(Arrays.asList(elements));
    }

    class Section extends DefaultContainerMarkup {
        private final String id;
        private final String title;

        public Section(String id, String title, Markup content) {
            super(content);
            this.id = id;
            this.title = title;
        }

        public String getId() {
            return id;
        }

        public String getTitle() {
            return title;
        }

        @Override
        public Section replaceContent(Markup newContent) {
            return new Section(id, title, newContent);
        }
    }

    class Application implements Markup {
        private final String scope;
        private final String id;
        private final String title;
        private final String anchor;
        private final Supplier<Markup> content;

        public Application(String scope, String id, String title, String anchor, Supplier<Markup> content) {
            this.scope = scope;
            this.id = id;
            this.title = title;
            this.anchor = anchor;
            this.content = content;
        }

        public String getScope() {
            return scope;
        }

        public String getId() {
            return id;
        }

        public String getTitle() {
            return title;
        }

        public String getAnchor() {
            return anchor;
        }

        public Supplier<Markup> getContent() {
            return content;
        }
    }

    class Block extends DefaultContainerMarkup {
        public Block(Markup content) {
            super(content);
        }

        @Override
        public Block replaceContent(Markup newContent) {
            return new Block(newContent);
        }
    }

    class I extends DefaultContainerMarkup {
        public I(Markup content) {
            super(content);
        }

        @Override
        public I replaceContent(Markup newContent) {
            return new I(newContent);
        }
    }

    class B extends DefaultContainerMarkup {
        public B(Markup content) {
            super(content);
        }

        @Override
        public B replaceContent(Markup newContent) {
            return new B(newContent);
        }
    }

    static B b(String text) {
        return new B(textOrEmpty(text));
    }

    class Spoiler extends DefaultContainerMarkup {
        private final String title;

        public Spoiler(String title, Markup content) {
            super(content);
            this.title = title;
        }

        public String getTitle() {
            return title;
        }

        @Override
        public Spoiler replaceContent(Markup newContent) {
            return new Spoiler(title, newContent);
        }
    }

    class Popup extends DefaultContainerMarkup {
        private final String id;
        private final String anchor;
        private final String title;

        public Popup(String id, String anchor, String title, Markup content) {
            super(content);
            this.id = id;
            this.anchor = anchor;
            this.title = title;
        }

        public String getId() {
            return id;
        }

        public String getAnchor() {
            return anchor;
        }

        public String getTitle() {
            return title;
        }

        @Override
        public Popup replaceContent(Markup newContent) {
            return new Popup(id, anchor, title, newContent);
        }
    }

    class Header implements Markup {
        private final String content;

        public Header(String content) {
            this.content = content;
        }

        public String getContent() {
            return content;
        }
    }

    class Table implements NonLeafMarkup {
        private final List<Markup> headers;
        private final List<List<Markup>> rows;

        public Table(List<Markup> headers, List<List<Markup>> rows) {
            this.headers = headers;
            this.rows = rows;
        }

        public List<Markup> getHeaders() {
            return headers;
        }

        public List<List<Markup>> getRows() {
            return rows;
        }

        @Override
        public List<Markup> getChildren() {
            return Stream.concat(
                    headers.stream(),
                    rows.stream().flatMap(Collection::stream)
            ).collect(Collectors.toList());
        }

        @Override
        public Table mapContents(Function<Markup, Markup> map) {
            List<Markup> newHeaders = headers.stream().map(map).collect(Collectors.toList());
            List<List<Markup>> newRows = rows.stream()
                    .map(r -> r.stream().map(map).collect(Collectors.toList()))
                    .collect(Collectors.toList());
            return new Table(newHeaders, newRows);
        }
    }

    static <T> Table table(List<T> data, String h1, String h2, Function<T, Markup> r1, Function<T, Markup> r2) {
        return new Table(
                Arrays.asList(Markup.textOrEmpty(h1), Markup.textOrEmpty(h2)),
                data.stream().map(r -> Arrays.asList(
                        r1.apply(r),
                        r2.apply(r)
                )).collect(Collectors.toList())
        );
    }

    static <T> Table table(List<T> data, String h1, String h2, String h3,
                           Function<T, Markup> r1, Function<T, Markup> r2, Function<T, Markup> r3) {
        return new Table(
                Arrays.asList(Markup.textOrEmpty(h1), Markup.textOrEmpty(h2), Markup.textOrEmpty(h3)),
                data.stream().map(r -> Arrays.asList(
                        r1.apply(r),
                        r2.apply(r),
                        r3.apply(r)
                )).collect(Collectors.toList())
        );
    }

    static <T> Table table(List<T> data, String h1, String h2, String h3, String h4,
                           Function<T, Markup> r1, Function<T, Markup> r2, Function<T, Markup> r3, Function<T, Markup> r4) {
        return new Table(
                Arrays.asList(Markup.textOrEmpty(h1), Markup.textOrEmpty(h2), Markup.textOrEmpty(h3), Markup.textOrEmpty(h4)),
                data.stream().map(r -> Arrays.asList(
                        r1.apply(r),
                        r2.apply(r),
                        r3.apply(r),
                        r4.apply(r)
                )).collect(Collectors.toList())
        );
    }

    static <T> Table table(List<T> data, String h1, String h2, String h3, String h4, String h5,
                           Function<T, Markup> r1, Function<T, Markup> r2, Function<T, Markup> r3, Function<T, Markup> r4, Function<T, Markup> r5) {
        return new Table(
                Arrays.asList(Markup.textOrEmpty(h1), Markup.textOrEmpty(h2), Markup.textOrEmpty(h3), Markup.textOrEmpty(h4), Markup.textOrEmpty(h5)),
                data.stream().map(r -> Arrays.asList(
                        r1.apply(r),
                        r2.apply(r),
                        r3.apply(r),
                        r4.apply(r),
                        r5.apply(r)
                )).collect(Collectors.toList())
        );
    }

    class Tabs implements NonLeafMarkup {
        private final List<Pair<String, Markup>> pages;

        public Tabs(List<Pair<String, Markup>> pages) {
            this.pages = pages;
        }

        public List<Pair<String, Markup>> getPages() {
            return pages;
        }

        @Override
        public List<Markup> getChildren() {
            return pages.stream().map(Pair::getRight).collect(Collectors.toList());
        }

        @Override
        public Tabs mapContents(Function<Markup, Markup> map) {
            return new Tabs(
                    pages.stream()
                            .map(p -> Pair.of(p.getLeft(), map.apply(p.getRight())))
                            .collect(Collectors.toList())
            );
        }
    }

    class UL implements CollectionMarkup {
        private final List<Markup> items;

        public UL(List<Markup> items) {
            this.items = items;
        }

        @Override
        public List<Markup> getItems() {
            return items;
        }

        @Override
        public UL replaceItems(List<Markup> newItems) {
            return new UL(newItems);
        }
    }

    class Link implements Markup {
        private final String text;
        private final String link;

        public Link(String text, String link) {
            this.text = text;
            this.link = link;
        }

        public String getText() {
            return text;
        }

        public String getLink() {
            return link;
        }
    }

    @Deprecated
    class DocumentedJson implements Markup {
        private final JsonValueWriter json;

        public DocumentedJson(JsonValueWriter json) {
            this.json = json;
        }

        public JsonValueWriter getJson() {
            return json;
        }
    }

    class JsonObject implements Markup {
        private final JsonValue json;

        public JsonObject(JsonValue json) {
            this.json = json;
        }

        public JsonValue getJson() {
            return json;
        }
    }

    class XmlObject implements Markup {
        private final XmlValue xml;

        public XmlObject(XmlValue xml) {
            this.xml = xml;
        }

        public XmlValue getXml() {
            return xml;
        }
    }

    Markup EMPTY = new Markup() {};

    static Markup textOrEmpty(String text) {
        return text == null ? EMPTY : new Text(text);
    }

    static <T> Collector<T, ?, Markup> reducer(Function<T, Markup> map) {
        return Collectors.mapping(map, reducer());
    }

    static Collector<Markup, ?, Markup> reducer() {
        return Collectors.reducing(EMPTY, Markup::concat);
    }
}
