package fr.upem.android.feedroid;

import android.util.Log;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Stack;

/**
 * A simple parser for Atom feeds.
 *
 * @author chilowi at univ-mlv.fr
 */
public class AtomParser {
    /**
     * An atom entry
     */
    public static class Entry implements Serializable {
        public final String id;
        public final String title;
        public final Date date;
        public final String summary;
        public final String url;

        private final String toStr;

        public Entry(String id, String title, Date date, String summary, String url) {
            this.id = id;
            this.title = title;
            this.date = date;
            this.summary = summary;
            this.url = url;

            this.toStr = "[" + id + ":" + title + "@" + date + ", " + url + ", " +
                    ((summary == null) ? "" : summary.substring(0, DIGESTED_SUMMARY_LENGTH)) + "...]";
        }

        public static final int DIGESTED_SUMMARY_LENGTH = 16;

        @Override
        public String toString() {
            return toStr;
        }
    }

    XmlPullParser parser;

    public AtomParser(Reader r) throws XmlPullParserException {
        parser = XmlPullParserFactory.newInstance().newPullParser();
        parser.setInput(r);
    }

    // Some feeds specify milliseconds, other not
    public static final DateFormat[] DATE_FORMATS = new DateFormat[]{
            new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz", Locale.US),
            new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSz", Locale.US)
    };

    // Try all the date formats to parse the date
    public static Date parseDate(String s) throws ParseException {
        ParseException e = null;
        for (DateFormat format : DATE_FORMATS) {
            try {
                return format.parse(s);
            } catch (ParseException e0) {
                e = e0;
            }
        }
        throw e;
    }

    public boolean parse(List<Entry> entries) throws IOException {
        Stack<String> tagStack = new Stack<>();
        String id = null, title = null, summary = null, link = null;
        Date date = null;
        for (boolean go = true; go; ) {
            int tag = -1;
            try {
                tag = parser.next();
            } catch (XmlPullParserException e) {
                Log.e("AtomParser", e.getMessage());
            }
            switch (tag) {
                case XmlPullParser.START_TAG: {
                    String tagName = parser.getName();
                    try {
                        boolean push = false;
                        switch (tagName) {
                            case "id":
                                id = parser.nextText();
                                break;
                            case "title":
                                title = parser.nextText();
                                break;
                            case "updated":
                                date = parseDate(parser.nextText());
                                break;
                            case "summary":
                                summary = parser.nextText();
                                break;
                            case "link":
                                link = parser.getAttributeValue(null, "href");
                                push = true;
                                break;
                            default:
                                push = true;
                                break;
                        }
                        if (push) {
                            tagStack.push(tagName);
                        }
                    } catch (XmlPullParserException e) {
                        Log.e("AtomParser", "Parsing exception encountered: ", e);
                        return false;
                    } catch (ParseException e) {
                        Log.e("AtomParser", e.getMessage());
                        return false;
                    }
                    break;
                }
                case XmlPullParser.END_TAG: {
                    String removed = tagStack.pop();
                    if (!removed.equals(parser.getName())) {
                        Log.e("AtomParser", "Encountered closing tag " + parser.getName() + " does not match the previously stacked tag " + removed);
                        return false;
                    }
                    if (removed.equals("entry")) {
                        entries.add(new Entry(id, title, date, summary, link));
                        id = null;
                        title = null;
                        date = null;
                        summary = null;
                        link = null;
                    }
                    if (tagStack.isEmpty())
                        go = false; // End of parsing
                    break;
                }
                case XmlPullParser.END_DOCUMENT:
                    return false;
                default:
                    break;
            }
        }
        return true;
    }

    public static void main(String[] args) throws Exception {
        if (args.length < 1) {
            throw new IndexOutOfBoundsException("A file must be specified as the single argument");
        }
        Reader r = new InputStreamReader(new FileInputStream(args[0]), "UTF-8");
        try {
            List<Entry> entries = new ArrayList<>();
            new AtomParser(r).parse(entries);
            for (Entry entry : entries)
                System.out.println(entry);
        } finally {
            r.close();
        }
    }
}
