/*
 * RSSParser.vala
 * 
 * RSS 2.0 feed parser
 */

public class RSSuper.RSSParser : Object {
    private string feed_url;
    private Feed? current_feed;
    private FeedItem? current_item;
    private string[] current_categories;
    private bool in_item;
    private bool in_channel;
    private bool in_image;
    private bool in_entry;

    public RSSParser() {}

    public ParseResult parse(string xml_content, string url) {
        this.feed_url = url;
        
        Xml.Doc* doc = Xml.Parser.parse_doc(xml_content);
        if (doc == null) {
            return ParseResult.error("Failed to parse XML document");
        }
        
        Xml.Node* root = doc->get_root_element();
        if (root == null) {
            delete doc;
            return ParseResult.error("No root element found");
        }
        
        string name = root->name;
        if (name == null || name != "rss") {
            delete doc;
            return ParseResult.error("Not an RSS feed: root element is '%s'".printf(name ?? "unknown"));
        }
        
        string? version = root->get_prop("version");
        if (version != null && version != "2.0" && version != "0.91" && version != "0.92") {
            delete doc;
            return ParseResult.error("Unsupported RSS version: %s".printf(version));
        }
        
        iterate_children(root);
        delete doc;
        
        if (current_feed == null) {
            return ParseResult.error("No channel element found");
        }
        
        current_feed.raw_url = url;
        
        return ParseResult.success(current_feed);
    }

    private void parse_element(Xml.Node* node) {
        string? name = node->name;
        if (name == null) {
            return;
        }
        
        switch (name) {
            case "channel":
                in_channel = true;
                current_feed = new Feed();
                current_categories = {};
                iterate_children(node);
                in_channel = false;
                break;
                
            case "item":
                in_item = true;
                current_item = new FeedItem();
                current_categories = {};
                iterate_children(node);
                if (current_item != null && current_item.title != "") {
                    if (current_item.id == "") {
                        current_item.id = current_item.guid ?? current_item.link ?? current_item.title;
                    }
                    if (current_feed != null) {
                        current_feed.add_item(current_item);
                    }
                }
                in_item = false;
                break;
                
            case "entry":
                in_entry = true;
                current_item = new FeedItem();
                current_categories = {};
                iterate_children(node);
                if (current_item != null && current_item.title != "") {
                    if (current_item.id == "") {
                        current_item.id = current_item.guid ?? current_item.link ?? current_item.title;
                    }
                    if (current_feed != null) {
                        current_feed.add_item(current_item);
                    }
                }
                in_entry = false;
                break;
                
            case "image":
                in_image = true;
                iterate_children(node);
                in_image = false;
                break;
                
            case "title":
                var text = node->get_content();
                if (text != null) {
                    text = text.strip();
                }
                if (in_item || in_entry) {
                    if (current_item != null && text != null) {
                        current_item.title = text;
                    }
                } else if (in_channel || in_image) {
                    if (current_feed != null && text != null) {
                        current_feed.title = text;
                    }
                }
                break;
                
            case "link":
                var text = node->get_content();
                if (text != null) {
                    text = text.strip();
                }
                if (in_channel) {
                    if (current_feed != null && current_feed.link == null && text != null) {
                        current_feed.link = text;
                    }
                } else if (in_item || in_entry) {
                    if (current_item != null && current_item.link == null && text != null) {
                        current_item.link = text;
                    }
                }
                break;
                
            case "description":
                var text = node->get_content();
                if (text != null) {
                    text = text.strip();
                }
                if (in_item || in_entry) {
                    if (current_item != null && current_item.description == null && text != null) {
                        current_item.description = text;
                    }
                } else if (in_channel) {
                    if (current_feed != null && text != null) {
                        current_feed.description = text;
                    }
                }
                break;
                
            case "subtitle":
                var text = node->get_content();
                if (text != null) {
                    text = text.strip();
                }
                if (current_feed != null && text != null) {
                    current_feed.subtitle = text;
                }
                break;
                
            case "language":
                var text = node->get_content();
                if (text != null) {
                    text = text.strip();
                }
                if (current_feed != null && text != null) {
                    current_feed.language = text;
                }
                break;
                
            case "lastBuildDate":
                var text = node->get_content();
                if (text != null) {
                    text = text.strip();
                }
                if (current_feed != null && text != null) {
                    current_feed.last_build_date = text;
                }
                break;
                
            case "updated":
                var text = node->get_content();
                if (text != null) {
                    text = text.strip();
                }
                if (current_feed != null && text != null) {
                    current_feed.updated = text;
                } else if (current_item != null && text != null) {
                    current_item.updated = text;
                }
                break;
                
            case "generator":
                var text = node->get_content();
                if (text != null) {
                    text = text.strip();
                }
                if (current_feed != null && text != null) {
                    current_feed.generator = text;
                }
                break;
                
            case "ttl":
                var text = node->get_content();
                if (text != null) {
                    text = text.strip();
                }
                if (current_feed != null && text != null) {
                    current_feed.ttl = int.parse(text);
                }
                break;
                
            case "author":
                var text = node->get_content();
                if (text != null) {
                    text = text.strip();
                }
                if (current_item != null && text != null) {
                    current_item.author = text;
                }
                break;
                
            case "dc:creator":
            case "creator":
                var text = node->get_content();
                if (text != null) {
                    text = text.strip();
                }
                if (current_item != null && current_item.author == null && text != null) {
                    current_item.author = text;
                }
                break;
                
            case "pubDate":
            case "published":
                var text = node->get_content();
                if (text != null) {
                    text = text.strip();
                }
                if (current_item != null && text != null) {
                    current_item.published = text;
                }
                break;
                
            case "guid":
            case "id":
                var text = node->get_content();
                if (text != null) {
                    text = text.strip();
                }
                if (current_item != null && current_item.guid == null && text != null) {
                    current_item.guid = text;
                }
                break;
                
            case "category":
                var text = node->get_content();
                if (text != null) {
                    text = text.strip();
                }
                if (current_item != null && text != null) {
                    var new_categories = new string[current_categories.length + 1];
                    for (var i = 0; i < current_categories.length; i++) {
                        new_categories[i] = current_categories[i];
                    }
                    new_categories[current_categories.length] = text;
                    current_categories = new_categories;
                    current_item.categories = current_categories;
                }
                break;
                
            case "enclosure":
                var url = node->get_prop("url");
                var type = node->get_prop("type");
                var length = node->get_prop("length");
                if (current_item != null && url != null) {
                    current_item.enclosure_url = url;
                    current_item.enclosure_type = type;
                    current_item.enclosure_length = length;
                }
                break;
                
            case "content:encoded":
            case "content":
                var text = node->get_content();
                if (text != null) {
                    text = text.strip();
                }
                if (current_item != null && text != null) {
                    current_item.content = text;
                }
                break;
                
            case "itunes:author":
                var text = node->get_content();
                if (text != null) {
                    text = text.strip();
                }
                if (current_item != null && current_item.author == null && text != null) {
                    current_item.author = text;
                }
                break;
                
            case "itunes:summary":
                var text = node->get_content();
                if (text != null) {
                    text = text.strip();
                }
                if (current_item != null) {
                    if (current_item.description == null && text != null) {
                        current_item.description = text;
                    }
                }
                break;
                
            case "url":
                if (in_image && current_feed != null) {
                    var text = node->get_content();
                    if (text != null) {
                        text = text.strip();
                    }
                    if (current_feed.link == null && text != null) {
                        current_feed.link = text;
                    }
                }
                break;
                
            default:
                iterate_children(node);
                break;
        }
    }

    private void iterate_children(Xml.Node* node) {
        Xml.Node* child = node->first_element_child();
        while (child != null) {
            parse_element(child);
            child = child->next_element_sibling();
        }
    }
}
