/*
 * This file is part of muCommander, http://www.mucommander.com
 *
 * muCommander is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * muCommander is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.mucommander.bookmark;

import com.mucommander.auth.CredentialsManager;
import com.mucommander.auth.CredentialsMapping;
import com.mucommander.commons.file.Credentials;
import com.mucommander.commons.file.FileURL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.SAXParserFactory;
import java.io.InputStream;
import java.net.MalformedURLException;


/**
 * This class takes care of parsing the bookmarks XML file and adding parsed {@link Bookmark} instances to {@link BookmarkManager}.
 *
 * @author Maxence Bernard
 */
class BookmarkParser extends DefaultHandler implements BookmarkConstants {
	private static final Logger LOGGER = LoggerFactory.getLogger(BookmarkParser.class);
	
    /** Variable used for XML parsing */
    private String bookmarkName;
    /** Variable used for XML parsing */
    private String bookmarkLocation;
    /** Variable used for XML parsing */
    private StringBuilder characters;
    /** Receives bookmarks events. */
    private BookmarkBuilder builder;
    /** muCommander version that was used to write the bookmarks file */
    private String version;


    /**
     * Creates a new BookmarkParser instance.
     */
    public BookmarkParser() {}

    /**
     * Parses the given XML bookmarks file. Should only be called by BookmarkManager.
     */
    void parse(InputStream in, BookmarkBuilder builder) throws Exception {
        this.builder = builder;
        characters   = new StringBuilder();
        SAXParserFactory.newInstance().newSAXParser().parse(in, this);
    }

    /**
     * Returns the muCommander version that was used to write the bookmarks file, <code>null</code> if it is unknown.
     * <p>
     * Note: the version attribute was introduced in muCommander 0.8.4.
     * </p>
     *
     * @return the muCommander version that was used to write the bookmarks file, <code>null</code> if it is unknown.
     */
    public String getVersion() {
        return version;
    }


    /* ------------------------ */
    /*  ContentHandler methods  */
    /* ------------------------ */

    @Override
    public void startDocument() throws SAXException {
        try {builder.startBookmarks();}
        catch(BookmarkException e) {throw new SAXException(e);}
    }

    @Override
    public void endDocument() throws SAXException {
        try {builder.endBookmarks();}
        catch(BookmarkException e) {throw new SAXException(e);}
    }

    /**
     * Method called when some PCDATA has been found in an XML node.
     */
    @Override
    public void characters(char[] ch, int start, int length) {characters.append(ch, start, length);}

    /**
     * Notifies the parser that a new XML node has been found.
     */
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        characters.setLength(0);

        if(qName.equals(ELEMENT_ROOT)) {
            version = attributes.getValue(ATTRIBUTE_VERSION);
        }
        else if(qName.equals(ELEMENT_BOOKMARK)) {
            // Reset parsing variables
            bookmarkName = null;
            bookmarkLocation = null;
        }
    }

    /**
     * Notifies the parser that an XML node has been closed.
     */
    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        if(qName.equals(ELEMENT_BOOKMARK)) {
            if(bookmarkName == null || bookmarkLocation == null) {
                LOGGER.info("Missing value, bookmark ignored: name=" + bookmarkName + " location=" + bookmarkLocation);
                return;
            }

            try {builder.addBookmark(bookmarkName, bookmarkLocation);}
            catch(BookmarkException e) {throw new SAXException(e);}
        }
        else if(qName.equals(ELEMENT_NAME)) {
            bookmarkName = characters.toString().trim();
        }
        else if(qName.equals(ELEMENT_LOCATION)) {
            bookmarkLocation = characters.toString().trim();
        }
        // Note: url element has been deprecated in 0.8 beta3 but is still checked against for upward compatibility.
        else if(qName.equals(ELEMENT_URL)) {
            // Until early 0.8 beta3 nightly builds, credentials were stored directly in the bookmark's url.
            // Now bookmark locations are free of credentials, these are stored in a dedicated credentials file where
            // the password is encrypted.
            try {
                FileURL url = FileURL.getFileURL(characters.toString().trim());
                Credentials credentials = url.getCredentials();

                // If the URL contains credentials, import them into CredentialsManager and remove credentials
                // from the bookmark's location
                if(credentials!=null) {
                    CredentialsManager.addCredentials(new CredentialsMapping(credentials, url, true));
                    bookmarkLocation = url.toString(false);
                }
                else {
                    bookmarkLocation = characters.toString().trim();
                }
            }
            catch(MalformedURLException e) {
                bookmarkLocation = characters.toString().trim();
            }
        }
    }
}
