/**
 * Note represents a single WordPress.com notification
 */
package org.edublogs.android.models;

import android.text.Html;
import android.text.Spanned;
import android.util.Log;
import android.text.style.QuoteSpan;
import android.text.SpannableStringBuilder;

import java.util.Map;
import java.util.HashMap;

import org.edublogs.android.util.Emoticons;
import org.edublogs.android.util.JSONUtil;
import org.edublogs.android.util.WPHtml;
import org.edublogs.android.util.WPHtmlTagHandler;
import org.json.JSONObject;
import org.json.JSONArray;
import org.json.JSONException;


public class Note {
    protected static final String TAG="NoteModel";
    public static final String UNKNOWN_TYPE="unknown";
    public static final String COMMENT_TYPE="comment";
    public static final String LIKE_TYPE="like";
    // Notes have different types of "templates" for displaying differently
    // this is not a canonical list but covers all the types currently in use
    public static final String SINGLE_LINE_LIST_TEMPLATE="single-line-list";
    public static final String MULTI_LINE_LIST_TEMPLATE="multi-line-list";
    public static final String BIG_BADGE_TEMPLATE="big-badge";
    // JSON keys and values for looking up values
    private static final String NOTE_ACTION_REPLY="replyto-comment";
    private static final String REPLY_CONTENT_PARAM_KEY="content";

    private Map<String,JSONObject> mActions;
    private Reply mReply;
    private JSONObject mNoteJSON;
    private SpannableStringBuilder mComment = new SpannableStringBuilder();
    /**
     * Create a note using JSON from REST API
     */
    public Note(JSONObject noteJSON){
        mNoteJSON = noteJSON;
        // get the comment ready if it's a comment type
        cleanupComment();
    }

    public String toString(){
        return getSubject();
    }
    
    public JSONObject toJSONObject(){
        return mNoteJSON;
    }
    public String getId(){
        return queryJSON("id", "0");
    }
    public String getType(){
        return queryJSON("type", UNKNOWN_TYPE);
    }
    public Boolean isType(String type){
        return getType().equals(type);
    }
    public Boolean isCommentType(){
        return isType(COMMENT_TYPE);
    }
    public String getSubject(){
        String text = queryJSON("subject.text", "").trim();
        if (text.equals("")) {
            text = queryJSON("subject.html", "");
        }
        return Html.fromHtml(text).toString();
    }
    public String getIconURL(){
        return queryJSON("subject.icon", "");
    }
    /**
     * Removes HTML and cleans up newlines and whitespace
     */
    public String getCommentPreview(){
        return getCommentBody().toString().replaceAll("\uFFFC", "").replace("\n", " ").replaceAll("[\\s]{2,}", " ").trim();
    }
    /**
     * Gets the comment's text with getCommentText() and sends it through HTML.fromHTML
     */
    public Spanned getCommentBody(){
        return mComment;
    }
    /**
     * For a comment note the text is in the body object's last item. It currently
     * is only provided in HTML format.
     */
    public String getCommentText(){
        return queryJSON("body.items[last].html", "");
    }
    /**
     * The inverse of isRead
     */
    public Boolean isUnread(){
        return !isRead();
    }
    /**
     * A note can have an "unread" of 0 or more ("likes" can have unread of 2+) to indicate the
     * quantity of likes that are "unread" within the single note. So for a note to be "read" it
     * should have "0"
     */
    public Boolean isRead(){
        return getUnreadCount().equals("0");
    }
    /**
     * For some reason the unread count is a string in the JSON API but is truly representd
     * by an Integer. We can handle a simple string.
     */
    public String getUnreadCount(){
        return queryJSON("unread", "0");
    }
    /**
     * 
     */
    public void setUnreadCount(String count){
        try {
            mNoteJSON.putOpt("unread", count);            
        } catch (JSONException e){
            Log.e(TAG, "Failed to set unread property", e);
        }
    }
    public Reply buildReply(String content){
        JSONObject replyAction = getActions().get(NOTE_ACTION_REPLY);
        String restPath = JSONUtil.queryJSON(replyAction, "params.rest_path", "");
        Log.d(TAG, String.format("Search actions %s", restPath));
        Reply reply = new Reply(this, String.format("%s/replies/new", restPath), content);
        return reply;
    }
    /**
     * Get the timestamp provided by the API for the note.
     */
    public String getTimestamp(){
        return queryJSON("timestamp", "");
    }
    public String getTemplate(){
        return queryJSON("body.template", "");
    }
    public Boolean isMultiLineListTemplate(){
        return getTemplate().equals(MULTI_LINE_LIST_TEMPLATE);
    }
    public Boolean isSingleLineListTemplate(){
        return getTemplate().equals(SINGLE_LINE_LIST_TEMPLATE);
    }
    public Boolean isBigBadgeTemplate(){
        return getTemplate().equals(BIG_BADGE_TEMPLATE);
    }
    public Map<String,JSONObject> getActions(){
        if (mActions == null) {
            try {
                JSONArray actions = queryJSON("body.actions", new JSONArray());
                mActions = new HashMap<String,JSONObject>(actions.length());
                for (int i=0; i<actions.length(); i++) {
                    JSONObject action = actions.getJSONObject(i);
                    String actionType = JSONUtil.queryJSON(action, "type", "");
                    if (!actionType.equals("")) {
                        mActions.put(actionType, action);
                    }
                }
            } catch (JSONException e) {
                Log.e(TAG, "Could not find actions", e);
                mActions = new HashMap<String,JSONObject>();
            }
        }
        return mActions;
    }
    /**
     * Prepares the comment HTML for being displayed. Cleans up emoticons.
     * 
     * TODO: Caching comment images
     */
    protected void cleanupComment(){
        if (isCommentType()) {
            mComment = Note.prepareHtml(getCommentText());
        }
    }
    protected Object queryJSON(String query){
        Object defaultObject = "";
        return JSONUtil.queryJSON(this.toJSONObject(), query, defaultObject);
    }
    /**
     * Rudimentary system for pulling an item out of a JSON object hierarchy
     */
    public <U> U queryJSON(String query, U defaultObject){
        return JSONUtil.queryJSON(this.toJSONObject(), query, defaultObject);
    }
    /**
     * Represents a user replying to a note. Holds
     */
    public static class Reply {
        private Note mNote;
        private String mContent;
        private String mRestPath;
        private JSONObject mCommentJson;
        
        Reply(Note note, String restPath, String content){
            mNote = note;
            mRestPath = restPath;
            mContent = content;
        }
        public String getContent(){
            return mContent;
        }
        public Note getNote(){
            return mNote;
        }
        public String getUrl(){
            if (isComplete()) {
                return JSONUtil.queryJSON(mCommentJson, "URL", "");
            }
            return null;
        }
        public String getAvatarUrl(){
            if (isComplete()) {
                return JSONUtil.queryJSON(mCommentJson, "author.avatar_URL", "");
            } else {
                return "";
            }
        }
        /**
         * Passes through Html.fromHtml to remove markup and replaces smilies with emoji
         */
        public String getCommentPreview(){
            if (isComplete()) {
                String text = JSONUtil.queryJSON(mCommentJson, "content", "");
                SpannableStringBuilder html = (SpannableStringBuilder) Html.fromHtml(text);
                return Emoticons.replaceEmoticonsWithEmoji(html).toString().trim();
            } else {
                return "";
            }
        }
        public String getRestPath(){
            return mRestPath;
        }
        public boolean isComplete(){
            return mCommentJson != null;
        }
        public JSONObject getCommentJson(){
            return mCommentJson;
        }
        public void setCommentJson(JSONObject commentJson){
            mCommentJson = commentJson;
        }
    }
    
    /**
     * Replaces emoticons with emoji
     */
    public static SpannableStringBuilder prepareHtml(String text){
        SpannableStringBuilder html = (SpannableStringBuilder) Html.fromHtml(text, null, new WPHtmlTagHandler());
        Emoticons.replaceEmoticonsWithEmoji(html);
        QuoteSpan spans[] = html.getSpans(0, html.length(), QuoteSpan.class);
        for (QuoteSpan span : spans) {
            html.setSpan(new WPHtml.WPQuoteSpan(), html.getSpanStart(span), html.getSpanEnd(span), html.getSpanFlags(span));
            html.removeSpan(span);
        }
        return html;
    }
}