package com.hdweiss.morgand.data.dao;

import android.text.TextUtils;

import com.hdweiss.morgand.data.OrgCalendarEntry;
import com.hdweiss.morgand.data.OrgNodeUtils;
import com.hdweiss.morgand.settings.PreferenceUtils;
import com.j256.ormlite.dao.ForeignCollection;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.field.ForeignCollectionField;
import com.j256.ormlite.stmt.Where;
import com.j256.ormlite.table.DatabaseTable;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@DatabaseTable(tableName = "OrgNodes")
public class OrgNode {

    public enum Type {
        File, Directory, Date, Headline, Body, Drawer, Checkbox, Setting, Table
    }

    public enum State {
        Clean, Updated, Added, Deleted
    }

    @DatabaseField(generatedId = true)
    public int Id;

    public static final String PARENT_FIELD_NAME = "parent";
    @DatabaseField(foreign = true, columnName = PARENT_FIELD_NAME, foreignAutoRefresh=true, canBeNull=true, maxForeignAutoRefreshLevel=3)
    public OrgNode parent;

    @ForeignCollectionField(eager = false, foreignFieldName = PARENT_FIELD_NAME)
    public ForeignCollection<OrgNode> children;

    @DatabaseField
    public Type type;

    public static final String LEVEL_FIELD_NAME = "level";
    @DatabaseField(columnName = LEVEL_FIELD_NAME)
    public int level = 0;

    public static final String LINENUMBER_FIELD_NAME = "lineNumber";
    @DatabaseField(columnName =  LINENUMBER_FIELD_NAME)
    public int lineNumber = -1;

    public static final String STATE_FIELD_NAME = "state";
    @DatabaseField(columnName = STATE_FIELD_NAME)
    public State state = State.Clean;

    public static final String FILE_FIELD_NAME = "file";
    @DatabaseField(foreign =  true, columnName = FILE_FIELD_NAME)
    public OrgFile file;


    public static final String TITLE_FIELD_NAME = "title";
    @DatabaseField(columnName = TITLE_FIELD_NAME)
    public String title = "";

    @DatabaseField
    public String tags = "";

    @DatabaseField
    public String inheritedTags = "";


    public String toString() {
        if (type != Type.Headline)
            return title;

        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < level; i++)
            builder.append("*");
        builder.append(" ");

        if (TextUtils.isEmpty(title) == false) {
            builder.append(title.trim());
            builder.append("\t" + tags);
        }
        return builder.toString();
    }

    public String toStringRecursively() {
        StringBuilder builder = new StringBuilder(toString());

        for(OrgNode child : children)
            builder.append("\n").append(child.toStringRecursively());

        return builder.toString();
    }


    public boolean isEditable() {
        if (type == Type.File || type == Type.Directory)
            return false;

        if (file.isEditable() == false)
            return false;

        return true;
    }

    public String getProperty(String propertyName) {
        if (type == Type.Headline) {
            Pattern propertyPattern = Pattern.compile(":" + propertyName + ":\\s+(.+)");
            for(OrgNode child: children) {
                if (child.type == Type.Drawer) {
                    Matcher matcher = propertyPattern.matcher(child.title);
                    if (matcher.find()) {
                        return matcher.group(1).trim();
                    }
                }
            }

            return null;
        }

        if (parent != null)
            return parent.getProperty(propertyName);
        return null;
    }

    public String getTitle() {
        if (type != Type.Headline) {
            if (parent != null) {
                return parent.getTitle();
            }
            else {
                return "";
            }
        }

        return title;
    }

    public String getTodo() {
        if (type == Type.Headline) {

            Matcher matcher = OrgNodeUtils.headingPattern.matcher(title);
            if (matcher.find())
                return matcher.group(1);
        }
        return "";
    }

    public String getBody() {
        if (type == Type.Headline) {
            StringBuilder body = new StringBuilder();
            for(OrgNode child: children) {
                if (child.type == Type.Body)
                    body.append(child.title);
            }
            return body.toString();
        } else if (parent != null)
            return  parent.getBody();

        return title;
    }

    public ArrayList<OrgNode> getDisplayChildren() {
        ArrayList<OrgNode> nodes = new ArrayList<OrgNode>();
        boolean showSettings = PreferenceUtils.showSettings();
        boolean showDrawers = PreferenceUtils.showDrawers();
        for(OrgNode node: children) {
            if (node.state == State.Deleted)
                continue;

            switch (node.type) {
                case Drawer:
                    if (showDrawers)
                        nodes.add(node);
                    break;

                case Setting:
                    if (showSettings)
                        nodes.add(node);
                    break;

                default:
                    nodes.add(node);
            }
        }
        return nodes;
    }

    public OrgNode addChild(Type type) {
        OrgNode node = new OrgNode();
        node.parent = this;
        node.file = this.file;
        node.type = type;
        node.level = this.level + 1;
        node.inheritedTags = OrgNodeUtils.combineTags(tags, inheritedTags, PreferenceUtils.getExcludedTags());
        return node;
    }

    public OrgNode addChild(Type type, String title) {
        OrgNode node = addChild(type);
        node.title = title;

        OrgNodeRepository.create(node);
        return node;
    }

    public int getNextNodeLineNumber() {
        try {
            Where<OrgNode, Integer> where = OrgNodeRepository.queryBuilder().orderBy(LINENUMBER_FIELD_NAME, true).where();
            where.eq(FILE_FIELD_NAME, file).and().gt(LINENUMBER_FIELD_NAME, lineNumber);
            OrgNode node = where.queryForFirst();
            if (node != null)
                return node.lineNumber;
            else
                return Integer.MAX_VALUE; // Current node seems to be last in file
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return -1;
    }

    /**
     * @return line number of next node on same or higher level than current node.
     */
    public int getSiblingLineNumber() {
        try {
            Where<OrgNode, Integer> where = OrgNodeRepository.queryBuilder().orderBy(LINENUMBER_FIELD_NAME, true).where();
            where.eq(FILE_FIELD_NAME, file).and().gt(LINENUMBER_FIELD_NAME, lineNumber).and().le(LEVEL_FIELD_NAME, level);
            OrgNode node = where.queryForFirst();
            if (node != null) {
                if (node.type != Type.File)
                    return node.lineNumber;
            }
            else
                return Integer.MAX_VALUE; // Current node seems to be last in file
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return -1;
    }

    public boolean isNodeWritable() {
        if (parent != null) {
            if (parent.state == State.Added)
                return false;

            if (parent.state == State.Deleted)
                return false;

            return parent.isNodeWritable();
        }

        return true;
    }

    public List<OrgCalendarEntry> getOrgNodeDates() {
        ArrayList<OrgCalendarEntry> dates = new ArrayList<OrgCalendarEntry>();

        Matcher matcher = OrgNodeUtils.dateMatcher.matcher(title);
        while(matcher.find()) {
            String type = matcher.group(1);
            String startDate = matcher.group(2);
            String endDate = matcher.group(3); // TODO Support day ranges

            try {
                OrgCalendarEntry orgCalendarEntry = new OrgCalendarEntry(startDate);
                orgCalendarEntry.type = type != null ? type : "";

                orgCalendarEntry.setTitle(getTitle());
                dates.add(orgCalendarEntry);
            } catch (IllegalArgumentException ex) {}
        }

        return dates;
    }


    public static class OrgNodeCompare implements Comparator<OrgNode> {
        @Override
        public int compare(OrgNode node1, OrgNode node2) {
            return node1.title.compareTo(node2.title);
        }
    }
}