package jp.sourceforge.nicoro;

import static jp.sourceforge.nicoro.Log.LOG_TAG;

import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

//import android.os.Parcel;
//import android.os.Parcelable;
import android.util.Xml;

import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.regex.Pattern;

public class RssLoader extends HttpXmlLoader {
//    private static final boolean DEBUG_LOGD = Release.IS_DEBUG & true;
    private static final boolean DEBUG_LOGV_PARSE = Release.IS_DEBUG & false;
    private static final boolean DEBUG_LOGD_PARSE = Release.IS_DEBUG & true;

    private static final String AMPERSAND_SIGN = "&";
    private static final String AMPERSAND_HTML = "&amp;";

    private String mUrl;
    private String mCookieUserSession;

    private CallbackMessage<RssLoader, RssLoader> mCallbackMessage;

    private Pattern mPatternDescriptionReplacePre = Pattern.compile(AMPERSAND_SIGN);
    private Pattern mPatternDescriptionReplacePost = Pattern.compile(AMPERSAND_HTML);
    private StringBuilder mBuilderCache;

    private String mLastErrorMessage;

    // XML解析データ
    private String mTitle;
    private String mLink;
    // FIXME ViewPagerのFragment復帰でどうしてもクラッシュすることがあるので、いったん実装を変える
    public static class Item implements /*Parcelable,*/ Serializable {
        /**
         *
         */
        private static final long serialVersionUID = -6711245102822919110L;

        public String title;
        public String link;
        public String thumbnail;
        public String description;
        public String infoNumber;
        public String infoLength;
        public String infoDate;
        public String infoTotalView;
        public String infoTotalRes;
        public String infoTotalMylist;
        public String infoHourlyView;
        public String infoHourlyRes;
        public String infoHourlyMylist;
        public String infoDailyView;
        public String infoDailyRes;
        public String infoDailyMylist;
        public String infoWeeklyView;
        public String infoWeeklyRes;
        public String infoWeeklyMylist;
        public String infoMonthlyView;
        public String infoMonthlyRes;
        public String infoMonthlyMylist;

        public Item() {
        }

//        @Override
//        public int describeContents() {
//            return 0;
//        }
//
//        @Override
//        public void writeToParcel(Parcel dest, int flags) {
//            dest.writeString(title);
//            dest.writeString(link);
//            dest.writeString(thumbnail);
//            dest.writeString(description);
//            dest.writeString(infoNumber);
//            dest.writeString(infoLength);
//            dest.writeString(infoDate);
//            dest.writeString(infoTotalView);
//            dest.writeString(infoTotalRes);
//            dest.writeString(infoTotalMylist);
//            dest.writeString(infoHourlyView);
//            dest.writeString(infoHourlyRes);
//            dest.writeString(infoHourlyMylist);
//            dest.writeString(infoDailyView);
//            dest.writeString(infoDailyRes);
//            dest.writeString(infoDailyMylist);
//            dest.writeString(infoWeeklyView);
//            dest.writeString(infoWeeklyRes);
//            dest.writeString(infoWeeklyMylist);
//            dest.writeString(infoMonthlyView);
//            dest.writeString(infoMonthlyRes);
//            dest.writeString(infoMonthlyMylist);
//        }
//
//        public static final Parcelable.Creator<RssLoader.Item> CREATOR =
//                new Creator<RssLoader.Item>() {
//            @Override
//            public Item[] newArray(int size) {
//                return new Item[size];
//            }
//
//            @Override
//            public Item createFromParcel(Parcel source) {
//                return new Item(source);
//            }
//        };
//
//        Item(Parcel source) {
//            title = source.readString();
//            link = source.readString();
//            thumbnail = source.readString();
//            description = source.readString();
//            infoNumber = source.readString();
//            infoLength = source.readString();
//            infoDate = source.readString();
//            infoTotalView = source.readString();
//            infoTotalRes = source.readString();
//            infoTotalMylist = source.readString();
//            infoHourlyView = source.readString();
//            infoHourlyRes = source.readString();
//            infoHourlyMylist = source.readString();
//            infoDailyView = source.readString();
//            infoDailyRes = source.readString();
//            infoDailyMylist = source.readString();
//            infoWeeklyView = source.readString();
//            infoWeeklyRes = source.readString();
//            infoWeeklyMylist = source.readString();
//            infoMonthlyView = source.readString();
//            infoMonthlyRes = source.readString();
//            infoMonthlyMylist = source.readString();
//        }
    }
    private ArrayList<Item> mItems = new ArrayList<Item>();

    public RssLoader(String url, String cookieUserSession) {
        mUrl = url;
        mCookieUserSession = cookieUserSession;
    }

    public void registerCallback(CallbackMessage<RssLoader, RssLoader> callback) {
        mCallbackMessage = callback;
    }

    public String getTitle() {
        return mTitle;
    }
    public String getLink() {
        return mLink;
    }
    public ArrayList<RssLoader.Item> getItems() {
        return mItems;
    }

    public String getLastErrorMessage() {
        return mLastErrorMessage;
    }

    @Override
    protected HttpUriRequest createRequest() {
        HttpGet httpRequest = new HttpGet(mUrl);
        httpRequest.addHeader("Cookie", mCookieUserSession);
        return httpRequest;
    }

    @Override
    protected boolean createDataFromXml(String xmlBody) {
        mBuilderCache = new StringBuilder();
        XmlPullParser pullParser = Xml.newPullParser();
        try {
            pullParser.setInput(new StringReader(xmlBody));

            int next;
            String name = null;
            while ((next = pullParser.next()) != XmlPullParser.END_DOCUMENT) {
                if (DEBUG_LOGV_PARSE) {
                    Log.v(LOG_TAG, Log.buf().append("next=").append(next).toString());
                }
                switch (next) {
                case XmlPullParser.START_TAG:
                    name = pullParser.getName();
                    if (DEBUG_LOGD_PARSE) {
                        XmlLoader.logStartTag(pullParser, name);
                    }
                    if ("channel".equals(name)) {
                        parseChannel(pullParser);
                    } else {
                        // その他のタグはとりあえず無視
                    }
                    break;
                case XmlPullParser.TEXT:
                    if (DEBUG_LOGD_PARSE) {
                        XmlLoader.logText(pullParser);
                    }
                    // その他のタグはとりあえず無視
                    break;
                case XmlPullParser.END_TAG:
                    name = pullParser.getName();
                    if (DEBUG_LOGD_PARSE) {
                        XmlLoader.logEndTag(pullParser, name);
                    }
                    // その他のタグはとりあえず無視
                    name = null;
                    break;
                default:
                    break;
                }
            }
        } catch (XmlPullParserException e) {
            Log.e(LOG_TAG, e.toString(), e);
        } catch (IOException e) {
            Log.e(LOG_TAG, e.toString(), e);
        }
        mBuilderCache = null;
        return true;
    }

    @Override
    protected void dispatchOnFinished() {
        mCallbackMessage.sendMessageSuccess(this);
    }

    @Override
    protected void dispatchOnOccurredError(String errorMessage) {
        mLastErrorMessage = errorMessage;
        mCallbackMessage.sendMessageError(this);
    }

    @Override
    protected boolean readAndCreateData(InputStream inDownload) throws IOException {
        String xmlBody = readEntityAndDecode(inDownload);
        return createDataFromXml(xmlBody);
    }

    @Override
    protected String getXmlParseErrorString() {
        return "RSS XML parse failed";
    }

    private void parseChannel(XmlPullParser pullParser)
    throws XmlPullParserException, IOException {
        int next;
        String name = null;
        while ((next = pullParser.next()) != XmlPullParser.END_DOCUMENT) {
            if (DEBUG_LOGV_PARSE) {
                Log.v(LOG_TAG, Log.buf().append("next=").append(next).toString());
            }
            switch (next) {
            case XmlPullParser.START_TAG:
                name = pullParser.getName();
                if (DEBUG_LOGD_PARSE) {
                    XmlLoader.logStartTag(pullParser, name);
                }
                if ("item".equals(name)) {
                    parseItem(pullParser);
                } else {
                    // その他のタグはとりあえず無視
                }
                break;
            case XmlPullParser.TEXT:
                if (DEBUG_LOGD_PARSE) {
                    XmlLoader.logText(pullParser);
                }
                String text = pullParser.getText();
                if ("title".equals(name)) {
                    mTitle = text;
                } else if ("link".equals(name)) {
                    mLink = text;
                } else {
                    // その他のタグはとりあえず無視
                }
                break;
            case XmlPullParser.END_TAG:
                name = pullParser.getName();
                if (DEBUG_LOGD_PARSE) {
                    XmlLoader.logEndTag(pullParser, name);
                }
                if ("channel".equals(name)) {
                    return;
                } else {
                    // その他のタグはとりあえず無視
                }
                name = null;
                break;
            default:
                break;
            }
        }
    }

    private void parseItem(XmlPullParser pullParser)
    throws XmlPullParserException, IOException {
        int next;
        String name = null;
        Item item = new Item();
        while ((next = pullParser.next()) != XmlPullParser.END_DOCUMENT) {
            if (DEBUG_LOGV_PARSE) {
                Log.v(LOG_TAG, Log.buf().append("next=").append(next).toString());
            }
            switch (next) {
            case XmlPullParser.START_TAG:
                name = pullParser.getName();
                if (DEBUG_LOGD_PARSE) {
                    XmlLoader.logStartTag(pullParser, name);
                }
                if ("description".equals(name)) {
                    parseItemDescription(pullParser, item);
                } else {
                    // その他のタグはとりあえず無視
                }
                break;
            case XmlPullParser.TEXT:
                if (DEBUG_LOGD_PARSE) {
                    XmlLoader.logText(pullParser);
                }
                String text = pullParser.getText();
                if ("title".equals(name)) {
                    item.title = text;
                } else if ("link".equals(name)) {
                    item.link = text;
                } else {
                    // その他のタグはとりあえず無視
                }
                break;
            case XmlPullParser.END_TAG:
                name = pullParser.getName();
                if (DEBUG_LOGD_PARSE) {
                    XmlLoader.logEndTag(pullParser, name);
                }
                if ("item".equals(name)) {
                    mItems.add(item);
                    return;
                } else {
                    // その他のタグはとりあえず無視
                }
                name = null;
                break;
            default:
                break;
            }
        }
    }

    private void parseItemDescription(XmlPullParser pullParser, Item item)
    throws XmlPullParserException, IOException {
        int next;
        String name = null;
        // nextToken()でUnsupportedOperationExceptionが出る？
        while ((next = pullParser.next()) != XmlPullParser.END_DOCUMENT) {
            if (DEBUG_LOGV_PARSE) {
                Log.v(LOG_TAG, Log.buf().append("next=").append(next).toString());
            }
            switch (next) {
            case XmlPullParser.START_TAG:
                name = pullParser.getName();
                if (DEBUG_LOGD_PARSE) {
                    XmlLoader.logStartTag(pullParser, name);
                }
                // その他のタグはとりあえず無視
                break;
            case XmlPullParser.TEXT:
                if (DEBUG_LOGD_PARSE) {
                    XmlLoader.logText(pullParser);
                }
                String text = pullParser.getText();
//                if (text.startsWith("<![CDATA[")) {
//                    int start = "<![CDATA[".length();
//                    int end = text.indexOf("]]>");
//                    if (end >= start) {
//                        text = text.substring(start, end);
                mBuilderCache.setLength(0);
                text = mBuilderCache.append("<dummy>")
                    .append(mPatternDescriptionReplacePre.matcher(text)
                            .replaceAll(AMPERSAND_HTML))
                    .append("</dummy>")
                    .toString();
                        XmlPullParser pullParserCdata = Xml.newPullParser();
                        pullParserCdata.setInput(new StringReader(text));
                        parseItemDescriptionCdata(pullParserCdata, item);
//                    }
//                } else {
                    // その他のタグはとりあえず無視
//                }
                break;
            case XmlPullParser.END_TAG:
                name = pullParser.getName();
                if (DEBUG_LOGD_PARSE) {
                    XmlLoader.logEndTag(pullParser, name);
                }
                if ("description".equals(name)) {
                    return;
                } else {
                    // その他のタグはとりあえず無視
                }
                name = null;
                break;
//            case XmlPullParser.CDSECT:
//                XmlPullParser pullParserCdata = Xml.newPullParser();
//                pullParserCdata.setInput(new StringReader(pullParser.getText()));
//                parseItemDescriptionCdata(pullParserCdata, item);
//                break;
            default:
                break;
            }
        }
    }

    private void parseItemDescriptionCdata(XmlPullParser pullParser, Item item)
    throws XmlPullParserException, IOException {
        int next;
        String name = null;
        String classAttr = null;
        try {
            while ((next = pullParser.next()) != XmlPullParser.END_DOCUMENT) {
                if (DEBUG_LOGV_PARSE) {
                    Log.v(LOG_TAG, Log.buf().append("next=").append(next).toString());
                }
                switch (next) {
                case XmlPullParser.START_TAG:
                    name = pullParser.getName();
                    if (DEBUG_LOGD_PARSE) {
                        XmlLoader.logStartTag(pullParser, name);
                    }
                    classAttr = pullParser.getAttributeValue(null, "class");
                    if ("nico-thumbnail".equals(classAttr)) {
                        parseItemDescriptionCdataThumbnail(pullParser, item);
                    } else {
                        // その他のタグはとりあえず無視
                    }
                    break;
                case XmlPullParser.TEXT:
                    if (DEBUG_LOGD_PARSE) {
                        XmlLoader.logText(pullParser);
                    }
                    String text = pullParser.getText();
                    if ("nico-description".equals(classAttr)) {
                        item.description = mPatternDescriptionReplacePost.matcher(
                                text).replaceAll(AMPERSAND_SIGN);
                    } else if ("nico-info-number".equals(classAttr)) {
                        item.infoNumber = text;
                    } else if ("nico-info-length".equals(classAttr)) {
                        item.infoLength = text;
                    } else if ("nico-info-date".equals(classAttr)) {
                        item.infoDate = text;
                    } else if ("nico-info-total-view".equals(classAttr)) {
                        item.infoTotalView = text;
                    } else if ("nico-info-total-res".equals(classAttr)) {
                        item.infoTotalRes = text;
                    } else if ("nico-info-total-mylist".equals(classAttr)) {
                        item.infoTotalMylist = text;
                    } else if ("nico-info-hourly-view".equals(classAttr)) {
                        item.infoHourlyView = text;
                    } else if ("nico-info-hourly-res".equals(classAttr)) {
                        item.infoHourlyRes = text;
                    } else if ("nico-info-hourly-mylist".equals(classAttr)) {
                        item.infoHourlyMylist = text;
                    } else if ("nico-info-daily-view".equals(classAttr)) {
                        item.infoDailyView = text;
                    } else if ("nico-info-daily-res".equals(classAttr)) {
                        item.infoDailyRes = text;
                    } else if ("nico-info-daily-mylist".equals(classAttr)) {
                        item.infoDailyMylist = text;
                    } else if ("nico-info-weekly-view".equals(classAttr)) {
                        item.infoWeeklyView = text;
                    } else if ("nico-info-weekly-res".equals(classAttr)) {
                        item.infoWeeklyRes = text;
                    } else if ("nico-info-weekly-mylist".equals(classAttr)) {
                        item.infoWeeklyMylist = text;
                    } else if ("nico-info-monthly-view".equals(classAttr)) {
                        item.infoMonthlyView = text;
                    } else if ("nico-info-monthly-res".equals(classAttr)) {
                        item.infoMonthlyRes = text;
                    } else if ("nico-info-monthly-mylist".equals(classAttr)) {
                        item.infoMonthlyMylist = text;
                    } else {
                        // その他のタグはとりあえず無視
                    }
                    break;
                case XmlPullParser.END_TAG:
                    name = pullParser.getName();
                    if (DEBUG_LOGD_PARSE) {
                        XmlLoader.logEndTag(pullParser, name);
                    }
                    classAttr = null;
                    // その他のタグはとりあえず無視
                    name = null;
                    break;
                default:
                    break;
                }
            }
        } catch (XmlPullParserException e) {
            // エラーでやすい可能性があるため、catchして次の処理へ
            Log.e(LOG_TAG, e.toString(), e);
        }
    }

    private void parseItemDescriptionCdataThumbnail(XmlPullParser pullParser, Item item)
    throws XmlPullParserException, IOException {
        String startName = pullParser.getName();
        int startDepth = pullParser.getDepth();
        int next;
        String name = null;
        while ((next = pullParser.next()) != XmlPullParser.END_DOCUMENT) {
            if (DEBUG_LOGV_PARSE) {
                Log.v(LOG_TAG, Log.buf().append("next=").append(next).toString());
            }
            switch (next) {
            case XmlPullParser.START_TAG:
                name = pullParser.getName();
                if (DEBUG_LOGD_PARSE) {
                    XmlLoader.logStartTag(pullParser, name);
                }
                if ("img".equals(name)) {
                    item.thumbnail = pullParser.getAttributeValue(null, "src");
                } else {
                    // その他のタグはとりあえず無視
                }
                break;
            case XmlPullParser.TEXT:
                if (DEBUG_LOGD_PARSE) {
                    XmlLoader.logText(pullParser);
                }
                // その他のタグはとりあえず無視
                break;
            case XmlPullParser.END_TAG:
                name = pullParser.getName();
                if (DEBUG_LOGD_PARSE) {
                    XmlLoader.logEndTag(pullParser, name);
                }
                if (startDepth == pullParser.getDepth() && startName.equals(name)) {
                    return;
                }
                // その他のタグはとりあえず無視
                name = null;
                break;
            default:
                break;
            }
        }
    }
}
