// Copyright (c) 2017-2018, Tom Geiselmann
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY,WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

package com.none.tom.simplerssreader.opml;

import android.text.TextUtils;

import org.apache.commons.text.StringEscapeUtils;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;

import static com.none.tom.simplerssreader.opml.Constants.BODY;
import static com.none.tom.simplerssreader.opml.Constants.DATE_CREATED;
import static com.none.tom.simplerssreader.opml.Constants.DATE_MODIFIED;
import static com.none.tom.simplerssreader.opml.Constants.DOCS;
import static com.none.tom.simplerssreader.opml.Constants.EXPANSION_STATE;
import static com.none.tom.simplerssreader.opml.Constants.HEAD;
import static com.none.tom.simplerssreader.opml.Constants.OWNER_EMAIL;
import static com.none.tom.simplerssreader.opml.Constants.OWNER_ID;
import static com.none.tom.simplerssreader.opml.Constants.OWNER_NAME;
import static com.none.tom.simplerssreader.opml.Constants.RFC_822_EXTENDED;
import static com.none.tom.simplerssreader.opml.Constants.TITLE;
import static com.none.tom.simplerssreader.opml.Constants.VERT_SCROLL_STATE;
import static com.none.tom.simplerssreader.opml.Constants.WINDOW_BOTTOM;
import static com.none.tom.simplerssreader.opml.Constants.WINDOW_LEFT;
import static com.none.tom.simplerssreader.opml.Constants.WINDOW_RIGHT;
import static com.none.tom.simplerssreader.opml.Constants.WINDOW_TOP;

@SuppressWarnings({"unchecked", "unused", "WeakerAccess", "UnusedReturnValue"})
public class OPMLFile {
    private Head mHead;
    private Body mBody;

    public Head getHead() {
        return mHead;
    }

    public OPMLFile setHead(final Head head) {
        mHead = head;

        return this;
    }

    public Body getBody() {
        return mBody;
    }

    public OPMLFile setBody(final Body body) {
        mBody = body;

        return this;
    }

    public boolean isEmpty() {
        return mHead.isEmpty() || mBody.isEmpty();
    }

    @Override
    public String toString() {
        return "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
                "\n<opml version=\"2.0\">" +
                    mHead.toString() +
                    mBody.toString() +
                "\n</opml>";
    }

    public static class Head {
        private final LinkedHashMap<String, Object> mAttrs;

        private Head(final LinkedHashMap<String, Object> head) {
            mAttrs = head;
        }

        @Override
        public String toString() {
            final StringBuilder head = new StringBuilder("\n\t<").append(HEAD).append('>');

            for (final String element : mAttrs.keySet()) {
                head.append("\n\t\t").append(toString(element, mAttrs.get(element)));
            }

            return head.append("\n\t").append("</").append(HEAD).append('>').toString();
        }

        private <T> String toString(final String elementName, final T value) {
            final StringBuilder element = new StringBuilder("<").append(elementName).append('>');
            final String elementText;

            if (value instanceof DateTime) {
                elementText = DateTimeFormat.forPattern(RFC_822_EXTENDED[0]).print(new DateTime());
            } else if (value instanceof List) {
                final String expansionState = value.toString();
                elementText = expansionState.substring(1, expansionState.length() - 1)
                        .replaceAll("\\s", "");
            } else if (value instanceof String && !TextUtils.isEmpty((String) value)) {
                elementText = (String) value;
            } else if (value instanceof Integer && (Integer) value != 0) {
                elementText = String.valueOf(value);
            } else {
                elementText = null;
            }

            if (!TextUtils.isEmpty(elementText)) {
                element.append(elementText).append("</").append(elementName).append('>');
            } else {
                element.append("</").append(elementName).append('>');
            }

            return element.toString();
        }

        public DateTime getDateCreated() {
            return (DateTime) mAttrs.get(DATE_CREATED);
        }

        public DateTime getDateModified() {
            return (DateTime) mAttrs.get(DATE_MODIFIED);
        }

        public String getDocs(final String docs) {
            return StringEscapeUtils.unescapeXml((String) mAttrs.get(DOCS));
        }

        public List<Integer> getExpansionState(final List<Integer> expansionState) {
            return (List<Integer>) mAttrs.get(EXPANSION_STATE);
        }

        public String getTitle() {
            return StringEscapeUtils.unescapeXml((String) mAttrs.get(TITLE));
        }

        public String getOwnerEmail() {
            return (String) mAttrs.get(OWNER_EMAIL);
        }

        public String getOwnerId() {
            return StringEscapeUtils.unescapeXml((String) mAttrs.get(OWNER_ID));
        }

        public String getOwnerName() {
            return StringEscapeUtils.unescapeXml((String) mAttrs.get(OWNER_NAME));
        }

        public Integer getVertScrollState() {
            return (Integer) mAttrs.get(VERT_SCROLL_STATE);
        }

        public Integer getWindowBottom() {
            return (Integer) mAttrs.get(WINDOW_BOTTOM);
        }

        public Integer getWindowLeft() {
            return (Integer) mAttrs.get(WINDOW_LEFT);
        }

        public Integer getWindowRight() {
            return (Integer) mAttrs.get(WINDOW_RIGHT);
        }

        public Integer getWindowTop() {
            return (Integer) mAttrs.get(WINDOW_TOP);
        }

        public boolean isEmpty() {
            return mAttrs.isEmpty();
        }

        public static class Builder {
            private final LinkedHashMap<String, Object> mHead;

            public Builder() {
                mHead = new LinkedHashMap<>();
            }

            public Head build() {
                return new Head(mHead);
            }

            public Builder setDateCreated(final DateTime dateCreated) {
                mHead.put(DATE_CREATED, dateCreated);

                return this;
            }

            public Builder setDateModified(final DateTime dateModified) {
                mHead.put(DATE_MODIFIED, dateModified);

                return this;
            }

            public Builder setDocs(final String docs) {
                mHead.put(DOCS, StringEscapeUtils.escapeXml10(docs));

                return this;
            }

            public Builder setExpansionState(final List<Integer> expansionState) {
                mHead.put(EXPANSION_STATE, expansionState);

                return this;
            }

            public Builder setTitle(final String title) {
                mHead.put(TITLE, StringEscapeUtils.escapeXml10(title));

                return this;
            }

            public Builder setOwnerEmail(final String ownerEmail) {
                mHead.put(OWNER_EMAIL, ownerEmail);

                return this;
            }

            public Builder setOwnerId(final String ownerId) {
                mHead.put(OWNER_ID, StringEscapeUtils.escapeXml10(ownerId));

                return this;
            }

            public Builder setOwnerName(final String ownerName) {
                mHead.put(OWNER_NAME, StringEscapeUtils.escapeXml10(ownerName));

                return this;
            }

            public Builder setVertScrollState(final Integer vertScrollState) {
                mHead.put(VERT_SCROLL_STATE, vertScrollState);

                return this;
            }

            public Builder setWindowBottom(final Integer windowBottom) {
                mHead.put(WINDOW_BOTTOM, windowBottom);

                return this;
            }

            public Builder setWindowLeft(final Integer windowLeft) {
                mHead.put(WINDOW_LEFT, windowLeft);

                return this;
            }

            public Builder setWindowRight(final Integer windowRight) {
                mHead.put(WINDOW_RIGHT, windowRight);

                return this;
            }

            public Builder setWindowTop(final Integer windowTop) {
                mHead.put(WINDOW_TOP, windowTop);

                return this;
            }
        }
    }

    public static class Body {
        private final List<Object> mOutlines;

        Body(final List<Object> outlines) {
            mOutlines = outlines;
        }

        @Override
        public String toString() {
            final StringBuilder body = new StringBuilder("\n\t<").append(BODY).append('>');

            for (final Object outline : mOutlines) {
                if (outline instanceof Outline) {
                    body.append("\n");
                }
                body.append(outline.toString());
            }

            return body.append("\n\t</").append(BODY).append('>').toString();
        }

        public List<Object> getOutlines() {
            return mOutlines;
        }

        public boolean isEmpty() {
            return mOutlines.isEmpty();
        }

        public static class Builder {
            private final List<Object> mOutlines;

            public Builder() {
                mOutlines = new ArrayList<>();
            }

            public Body build() {
                return new Body(mOutlines);
            }

            public Builder addOutline(final Outline outline) {
                mOutlines.add(outline);

                return this;
            }

            public Builder addOutlineGroup(final OutlineGroup outlineGroup) {
                mOutlines.add(outlineGroup);

                return this;
            }
        }
    }
}
