/*
 * 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.ui.theme;

import java.awt.Color;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.io.InputStream;
import java.util.StringTokenizer;

import javax.xml.parsers.SAXParserFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * Loads theme instances from properly formatted XML files.
 * @author Nicolas Rinaudo
 */
class ThemeReader extends DefaultHandler implements ThemeXmlConstants {
	private static final Logger LOGGER = LoggerFactory.getLogger(ThemeReader.class);
	
    // - XML parser states ---------------------------------------------------------------
    // -----------------------------------------------------------------------------------
    /** Parsing hasn't started yet. */
    private static final int STATE_UNKNOWN                  = 0;
    /** Parsing the root element. */
    private static final int STATE_ROOT                     = 1;
    /** Parsing the table element.*/
    private static final int STATE_TABLE                    = 2;
    /** Parsing the shell element. */
    private static final int STATE_SHELL                    = 3;
    /** Parsing the editor element. */
    private static final int STATE_EDITOR                   = 4;
    /** Parsing the location bar element. */
    private static final int STATE_LOCATION_BAR             = 5;
    /** Parsing the shell.normal element. */
    private static final int STATE_SHELL_NORMAL             = 6;
    /** Parsing the shell.selected element. */
    private static final int STATE_SHELL_SELECTED           = 7;
    /** Parsing the editor.normal element. */
    private static final int STATE_EDITOR_NORMAL            = 8;
    /** Parsing the location bar.normal element. */
    private static final int STATE_LOCATION_BAR_NORMAL      = 9;
    /** Parsing the editor.selected element. */
    private static final int STATE_EDITOR_SELECTED          = 10;
    /** Parsing the location bar.selected element. */
    private static final int STATE_LOCATION_BAR_SELECTED    = 11;
    /** Parsing the shell_history element. */
    private static final int STATE_SHELL_HISTORY            = 12;
    /** Parsing the shell_history.normal element. */
    private static final int STATE_SHELL_HISTORY_NORMAL     = 13;
    /** Parsing the shell_history.selected element. */
    private static final int STATE_SHELL_HISTORY_SELECTED   = 14;
    /** Parsing the volume_label element. */
    private static final int STATE_STATUS_BAR               = 15;
    private static final int STATE_HIDDEN                   = 16;
    private static final int STATE_HIDDEN_NORMAL            = 17;
    private static final int STATE_HIDDEN_SELECTED          = 18;
    private static final int STATE_FOLDER                   = 19;
    private static final int STATE_FOLDER_NORMAL            = 20;
    private static final int STATE_FOLDER_SELECTED          = 21;
    private static final int STATE_ARCHIVE                  = 22;
    private static final int STATE_ARCHIVE_NORMAL           = 23;
    private static final int STATE_ARCHIVE_SELECTED         = 24;
    private static final int STATE_SYMLINK                  = 25;
    private static final int STATE_SYMLINK_NORMAL           = 26;
    private static final int STATE_SYMLINK_SELECTED         = 27;
    private static final int STATE_MARKED                   = 28;
    private static final int STATE_MARKED_NORMAL            = 29;
    private static final int STATE_MARKED_SELECTED          = 30;
    private static final int STATE_FILE                     = 31;
    private static final int STATE_FILE_NORMAL              = 32;
    private static final int STATE_FILE_SELECTED            = 33;
    private static final int STATE_TABLE_NORMAL             = 34;
    private static final int STATE_TABLE_SELECTED           = 35;
    private static final int STATE_TABLE_ALTERNATE          = 36;
    private static final int STATE_TABLE_UNMATCHED          = 37;
    /** Parsing the quick list element. */
    private static final int STATE_QUICK_LIST               = 38;
    /** Parsing the quick list header element. */
    private static final int STATE_QUICK_LIST_HEADER        = 39;
    /** Parsing the quick list item element. */
    private static final int STATE_QUICK_LIST_ITEM          = 40;
    private static final int STATE_QUICK_LIST_ITEM_NORMAL   = 41;
    private static final int STATE_QUICK_LIST_ITEM_SELECTED = 42;


    // - Instance variables --------------------------------------------------------------
    // -----------------------------------------------------------------------------------
    /** Theme template that is currently being built. */
    private ThemeData     template;
    /** Current state of the XML parser. */
    private int           state;
    /** Used to ignore the content of an unknown tag. */
    private String        unknownElement;



    // - Initialisation ------------------------------------------------------------------
    // -----------------------------------------------------------------------------------
    /**
     * Creates a new theme reader.
     */
    private ThemeReader(ThemeData t) {
        template = t;
        state    = STATE_UNKNOWN;
    }

    /**
     * Attempts to read a theme from the specified input stream.
     * @param     in        where to read the theme from.
     * @param     template  template in which to store the data.
     * @exception Exception thrown if an error occured while reading the template.
     */
    public static void read(InputStream in, ThemeData template) throws Exception {SAXParserFactory.newInstance().newSAXParser().parse(in, new ThemeReader(template));}


    // - XML interaction -----------------------------------------------------
    // -----------------------------------------------------------------------
    /**
     * Notifies the reader that a new XML element is starting.
     */
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        // Ignores the content of unknown elements.
        if(unknownElement != null) {
            LOGGER.debug("Ignoring element " + qName);
            return;
        }

        // XML root element.
        if(qName.equals(ELEMENT_ROOT)) {
            if(state != STATE_UNKNOWN)
                traceIllegalDeclaration(ELEMENT_ROOT);
            state = STATE_ROOT;
        }

        // File table declaration.
        else if(qName.equals(ELEMENT_TABLE)) {
            if(state != STATE_ROOT)
                traceIllegalDeclaration(qName);
            state = STATE_TABLE;
        }

        // Shell declaration.
        else if(qName.equals(ELEMENT_SHELL)) {
            if(state != STATE_ROOT)
                traceIllegalDeclaration(qName);
            state = STATE_SHELL;
        }

        // Editor declaration.
        else if(qName.equals(ELEMENT_EDITOR)) {
            if(state != STATE_ROOT)
                traceIllegalDeclaration(qName);
            state = STATE_EDITOR;
        }

        // Location bar declaration.
        else if(qName.equals(ELEMENT_LOCATION_BAR)) {
            if(state != STATE_ROOT)
                traceIllegalDeclaration(qName);
            state = STATE_LOCATION_BAR;
        }
        
        // Quick list declaration.
        else if(qName.equals(ELEMENT_QUICK_LIST)) {
            if(state != STATE_ROOT)
                traceIllegalDeclaration(qName);
            state = STATE_QUICK_LIST;
        }

        // Shell history declaration.
        else if(qName.equals(ELEMENT_SHELL_HISTORY)) {
            if(state != STATE_ROOT)
                traceIllegalDeclaration(qName);
            state = STATE_SHELL_HISTORY;
        }

        // Volume label declaration.
        else if(qName.equals(ELEMENT_STATUS_BAR)) {
            if(state != STATE_ROOT)
                traceIllegalDeclaration(qName);
            state = STATE_STATUS_BAR;
        }

        else if(qName.equals(ELEMENT_HIDDEN)) {
            if(state != STATE_TABLE)
                traceIllegalDeclaration(qName);
            state = STATE_HIDDEN;
        }

        else if(qName.equals(ELEMENT_FOLDER)) {
            if(state != STATE_TABLE)
                traceIllegalDeclaration(qName);
            state = STATE_FOLDER;
        }

        else if(qName.equals(ELEMENT_ARCHIVE)) {
            if(state != STATE_TABLE)
                traceIllegalDeclaration(qName);
            state = STATE_ARCHIVE;
        }

        else if(qName.equals(ELEMENT_SYMLINK)) {
            if(state != STATE_TABLE)
                traceIllegalDeclaration(qName);
            state = STATE_SYMLINK;
        }

        else if(qName.equals(ELEMENT_MARKED)) {
            if(state != STATE_TABLE)
                traceIllegalDeclaration(qName);
            state = STATE_MARKED;
        }

        else if(qName.equals(ELEMENT_FILE)) {
            if(state != STATE_TABLE)
                traceIllegalDeclaration(qName);
            state = STATE_FILE;
        }

        else if(qName.equals(ELEMENT_ALTERNATE)) {
            if(state != STATE_TABLE)
                traceIllegalDeclaration(qName);
            state = STATE_TABLE_ALTERNATE;
        }
        
        // Header declaration.
        else if(qName.equals(ELEMENT_HEADER)) {
        	if(state == STATE_QUICK_LIST)
                state = STATE_QUICK_LIST_HEADER;
            else
                traceIllegalDeclaration(qName);
        }
        
        // Item declaration.
        else if(qName.equals(ELEMENT_ITEM)) {
        	if(state == STATE_QUICK_LIST)
                state = STATE_QUICK_LIST_ITEM;
            else
                traceIllegalDeclaration(qName);
        }

        // Normal element declaration.
        else if(qName.equals(ELEMENT_NORMAL)) {
            if(state == STATE_SHELL)
                state = STATE_SHELL_NORMAL;
            else if(state == STATE_EDITOR)
                state = STATE_EDITOR_NORMAL;
            else if(state == STATE_LOCATION_BAR)
                state = STATE_LOCATION_BAR_NORMAL;
            else if(state == STATE_SHELL_HISTORY)
                state = STATE_SHELL_HISTORY_NORMAL;
            else if(state == STATE_HIDDEN)
                state = STATE_HIDDEN_NORMAL;
            else if(state == STATE_FOLDER)
                state = STATE_FOLDER_NORMAL;
            else if(state == STATE_ARCHIVE)
                state = STATE_ARCHIVE_NORMAL;
            else if(state == STATE_SYMLINK)
                state = STATE_SYMLINK_NORMAL;
            else if(state == STATE_MARKED)
                state = STATE_MARKED_NORMAL;
            else if(state == STATE_FILE)
                state = STATE_FILE_NORMAL;
            else if(state == STATE_TABLE)
                state = STATE_TABLE_NORMAL;
            else if(state == STATE_QUICK_LIST_ITEM)
            	state = STATE_QUICK_LIST_ITEM_NORMAL;
            else
                traceIllegalDeclaration(qName);
        }

        // Selected element declaration.
        else if(qName.equals(ELEMENT_SELECTED)) {
            if(state == STATE_SHELL)
                state = STATE_SHELL_SELECTED;
            else if(state == STATE_EDITOR)
                state = STATE_EDITOR_SELECTED;
            else if(state == STATE_LOCATION_BAR)
                state = STATE_LOCATION_BAR_SELECTED;
            else if(state == STATE_SHELL_HISTORY)
                state = STATE_SHELL_HISTORY_SELECTED;
            else if(state == STATE_HIDDEN)
                state = STATE_HIDDEN_SELECTED;
            else if(state == STATE_FOLDER)
                state = STATE_FOLDER_SELECTED;
            else if(state == STATE_ARCHIVE)
                state = STATE_ARCHIVE_SELECTED;
            else if(state == STATE_SYMLINK)
                state = STATE_SYMLINK_SELECTED;
            else if(state == STATE_MARKED)
                state = STATE_MARKED_SELECTED;
            else if(state == STATE_FILE)
                state = STATE_FILE_SELECTED;
            else if(state == STATE_TABLE)
                state = STATE_TABLE_SELECTED;
            else if(state == STATE_QUICK_LIST_ITEM)
            	state = STATE_QUICK_LIST_ITEM_SELECTED;
            else
                traceIllegalDeclaration(qName);
        }

        // Font creation.
        else if(qName.equals(ELEMENT_FONT)) {
            if(state == STATE_SHELL)
                template.setFont(ThemeData.SHELL_FONT, createFont(attributes));
            else if(state == STATE_EDITOR)
                template.setFont(ThemeData.EDITOR_FONT, createFont(attributes));
            else if(state == STATE_LOCATION_BAR)
                template.setFont(ThemeData.LOCATION_BAR_FONT, createFont(attributes));
            else if(state == STATE_SHELL_HISTORY)
                template.setFont(ThemeData.SHELL_HISTORY_FONT, createFont(attributes));
            else if(state == STATE_STATUS_BAR)
                template.setFont(ThemeData.STATUS_BAR_FONT, createFont(attributes));
            else if(state == STATE_TABLE)
                template.setFont(ThemeData.FILE_TABLE_FONT, createFont(attributes));
            else if(state == STATE_QUICK_LIST_HEADER)
            	template.setFont(ThemeData.QUICK_LIST_HEADER_FONT, createFont(attributes));
            else if(state == STATE_QUICK_LIST_ITEM)
            	template.setFont(ThemeData.QUICK_LIST_ITEM_FONT, createFont(attributes));
            else
                traceIllegalDeclaration(qName);
        }

        // Unfocused background color.
        else if(qName.equals(ELEMENT_INACTIVE_BACKGROUND)) {
            if(state == STATE_TABLE_NORMAL)
                template.setColor(ThemeData.FILE_TABLE_INACTIVE_BACKGROUND_COLOR, createColor(attributes));
            else if(state == STATE_TABLE_SELECTED)
                template.setColor(ThemeData.FILE_TABLE_INACTIVE_SELECTED_BACKGROUND_COLOR, createColor(attributes));
            else if(state == STATE_TABLE_ALTERNATE)
                template.setColor(ThemeData.FILE_TABLE_INACTIVE_ALTERNATE_BACKGROUND_COLOR, createColor(attributes));
            else
                traceIllegalDeclaration(qName);
        }

        // Secondary background.
        else if(qName.equals(ELEMENT_SECONDARY_BACKGROUND)) {
            if(state == STATE_TABLE_SELECTED)
                template.setColor(ThemeData.FILE_TABLE_SELECTED_SECONDARY_BACKGROUND_COLOR, createColor(attributes));
            else if(state == STATE_QUICK_LIST_HEADER)
            	template.setColor(ThemeData.QUICK_LIST_HEADER_SECONDARY_BACKGROUND_COLOR, createColor(attributes));
            else
                traceIllegalDeclaration(qName);
        }

        // Inactive secondary background.
        else if(qName.equals(ELEMENT_INACTIVE_SECONDARY_BACKGROUND)) {
            if(state == STATE_TABLE_SELECTED)
                template.setColor(ThemeData.FILE_TABLE_INACTIVE_SELECTED_SECONDARY_BACKGROUND_COLOR, createColor(attributes));
            else
                traceIllegalDeclaration(qName);
        }

        // Unfocused foreground color.
        else if(qName.equals(ELEMENT_INACTIVE_FOREGROUND)) {
            if(state == STATE_FILE_NORMAL)
                template.setColor(ThemeData.FILE_INACTIVE_FOREGROUND_COLOR, createColor(attributes));
            else if(state == STATE_FOLDER_NORMAL)
                template.setColor(ThemeData.FOLDER_INACTIVE_FOREGROUND_COLOR, createColor(attributes));
            else if(state == STATE_ARCHIVE_NORMAL)
                template.setColor(ThemeData.ARCHIVE_INACTIVE_FOREGROUND_COLOR, createColor(attributes));
            else if(state == STATE_SYMLINK_NORMAL)
                template.setColor(ThemeData.SYMLINK_INACTIVE_FOREGROUND_COLOR, createColor(attributes));
            else if(state == STATE_HIDDEN_NORMAL)
                template.setColor(ThemeData.HIDDEN_FILE_INACTIVE_FOREGROUND_COLOR, createColor(attributes));
            else if(state == STATE_MARKED_NORMAL)
                template.setColor(ThemeData.MARKED_INACTIVE_FOREGROUND_COLOR, createColor(attributes));
            else if(state == STATE_FILE_SELECTED)
                template.setColor(ThemeData.FILE_INACTIVE_SELECTED_FOREGROUND_COLOR, createColor(attributes));
            else if(state == STATE_FOLDER_SELECTED)
                template.setColor(ThemeData.FOLDER_INACTIVE_SELECTED_FOREGROUND_COLOR, createColor(attributes));
            else if(state == STATE_ARCHIVE_SELECTED)
                template.setColor(ThemeData.ARCHIVE_INACTIVE_SELECTED_FOREGROUND_COLOR, createColor(attributes));
            else if(state == STATE_SYMLINK_SELECTED)
                template.setColor(ThemeData.SYMLINK_INACTIVE_SELECTED_FOREGROUND_COLOR, createColor(attributes));
            else if(state == STATE_HIDDEN_SELECTED)
                template.setColor(ThemeData.HIDDEN_FILE_INACTIVE_SELECTED_FOREGROUND_COLOR, createColor(attributes));
            else if(state == STATE_MARKED_SELECTED)
                template.setColor(ThemeData.MARKED_INACTIVE_SELECTED_FOREGROUND_COLOR, createColor(attributes));
            else
                traceIllegalDeclaration(qName);
        }

        // File table border color.
        else if(qName.equals(ELEMENT_BORDER)) {
            if(state == STATE_TABLE)
                template.setColor(ThemeData.FILE_TABLE_BORDER_COLOR, createColor(attributes));

            else if(state == STATE_STATUS_BAR)
                template.setColor(ThemeData.STATUS_BAR_BORDER_COLOR, createColor(attributes));

            else
                traceIllegalDeclaration(qName);
        }

        // File table inactive border color.
        else if(qName.equals(ELEMENT_INACTIVE_BORDER)) {
            if(state == STATE_TABLE)
                template.setColor(ThemeData.FILE_TABLE_INACTIVE_BORDER_COLOR, createColor(attributes));

            else
                traceIllegalDeclaration(qName);
        }

        // File table outline color.
        else if(qName.equals(ELEMENT_OUTLINE)) {
            if(state == STATE_TABLE)
                template.setColor(ThemeData.FILE_TABLE_SELECTED_OUTLINE_COLOR, createColor(attributes));
            else
                traceIllegalDeclaration(qName);
        }

        // File table inactive outline color.
        else if(qName.equals(ELEMENT_INACTIVE_OUTLINE)) {
            if(state == STATE_TABLE)
                template.setColor(ThemeData.FILE_TABLE_INACTIVE_SELECTED_OUTLINE_COLOR, createColor(attributes));
            else
                traceIllegalDeclaration(qName);
        }

        // Unmatched file table.
        else if(qName.equals(ELEMENT_UNMATCHED)) {
            if(state != STATE_TABLE)
                traceIllegalDeclaration(qName);
            state = STATE_TABLE_UNMATCHED;
        }

        // Background color.
        else if(qName.equals(ELEMENT_BACKGROUND)) {
            if(state == STATE_TABLE_NORMAL)
                template.setColor(ThemeData.FILE_TABLE_BACKGROUND_COLOR, createColor(attributes));
            else if(state == STATE_TABLE_SELECTED)
                template.setColor(ThemeData.FILE_TABLE_SELECTED_BACKGROUND_COLOR, createColor(attributes));
            else if(state == STATE_TABLE_ALTERNATE)
                template.setColor(ThemeData.FILE_TABLE_ALTERNATE_BACKGROUND_COLOR, createColor(attributes));
            else if(state == STATE_TABLE_UNMATCHED)
                template.setColor(ThemeData.FILE_TABLE_UNMATCHED_BACKGROUND_COLOR, createColor(attributes));

            else if(state == STATE_SHELL_NORMAL)
                template.setColor(ThemeData.SHELL_BACKGROUND_COLOR, createColor(attributes));
            else if(state == STATE_SHELL_SELECTED)
                template.setColor(ThemeData.SHELL_SELECTED_BACKGROUND_COLOR, createColor(attributes));

            else if(state == STATE_EDITOR_NORMAL)
                template.setColor(ThemeData.EDITOR_BACKGROUND_COLOR, createColor(attributes));
            else if(state == STATE_EDITOR_SELECTED)
                template.setColor(ThemeData.EDITOR_SELECTED_BACKGROUND_COLOR, createColor(attributes));

            else if(state == STATE_LOCATION_BAR_NORMAL)
                template.setColor(ThemeData.LOCATION_BAR_BACKGROUND_COLOR, createColor(attributes));
            else if(state == STATE_LOCATION_BAR_SELECTED)
                template.setColor(ThemeData.LOCATION_BAR_SELECTED_BACKGROUND_COLOR, createColor(attributes));

            else if(state == STATE_SHELL_HISTORY_NORMAL)
                template.setColor(ThemeData.SHELL_HISTORY_BACKGROUND_COLOR, createColor(attributes));
            else if(state == STATE_SHELL_HISTORY_SELECTED)
                template.setColor(ThemeData.SHELL_HISTORY_SELECTED_BACKGROUND_COLOR, createColor(attributes));

            else if(state == STATE_STATUS_BAR)
                template.setColor(ThemeData.STATUS_BAR_BACKGROUND_COLOR, createColor(attributes));
            
            else if(state == STATE_QUICK_LIST_HEADER)
            	template.setColor(ThemeData.QUICK_LIST_HEADER_BACKGROUND_COLOR, createColor(attributes));
            else if(state == STATE_QUICK_LIST_ITEM_NORMAL)
            	template.setColor(ThemeData.QUICK_LIST_ITEM_BACKGROUND_COLOR, createColor(attributes));
            else if(state == STATE_QUICK_LIST_ITEM_SELECTED)
            	template.setColor(ThemeData.QUICK_LIST_SELECTED_ITEM_BACKGROUND_COLOR, createColor(attributes));

            else
                traceIllegalDeclaration(qName);
        }

        // Progress bar color.
        else if(qName.equals(ELEMENT_PROGRESS)) {
            if(state == STATE_LOCATION_BAR)
                template.setColor(ThemeData.LOCATION_BAR_PROGRESS_COLOR, createColor(attributes));
            else
                traceIllegalDeclaration(qName);
        }

        // 'OK' color.
        else if(qName.equals(ELEMENT_OK)) {
            if(state == STATE_STATUS_BAR)
                template.setColor(ThemeData.STATUS_BAR_OK_COLOR, createColor(attributes));
            else
                traceIllegalDeclaration(qName);
        }

        // 'WARNING' color.
        else if(qName.equals(ELEMENT_WARNING)) {
            if(state == STATE_STATUS_BAR)
                template.setColor(ThemeData.STATUS_BAR_WARNING_COLOR, createColor(attributes));
            else
                traceIllegalDeclaration(qName);
        }

        // 'CRITICAL' color.
        else if(qName.equals(ELEMENT_CRITICAL)) {
            if(state == STATE_STATUS_BAR)
                template.setColor(ThemeData.STATUS_BAR_CRITICAL_COLOR, createColor(attributes));
            else
                traceIllegalDeclaration(qName);
        }

        // Text color.
        else if(qName.equals(ELEMENT_FOREGROUND)) {
            if(state == STATE_HIDDEN_NORMAL)
                template.setColor(ThemeData.HIDDEN_FILE_FOREGROUND_COLOR, createColor(attributes));
            else if(state == STATE_HIDDEN_SELECTED)
                template.setColor(ThemeData.HIDDEN_FILE_SELECTED_FOREGROUND_COLOR, createColor(attributes));

            else if(state == STATE_TABLE_UNMATCHED)
                template.setColor(ThemeData.FILE_TABLE_UNMATCHED_FOREGROUND_COLOR, createColor(attributes));

            else if(state == STATE_FOLDER_NORMAL)
                template.setColor(ThemeData.FOLDER_FOREGROUND_COLOR, createColor(attributes));
            else if(state == STATE_FOLDER_SELECTED)
                template.setColor(ThemeData.FOLDER_SELECTED_FOREGROUND_COLOR, createColor(attributes));

            else if(state == STATE_ARCHIVE_NORMAL)
                template.setColor(ThemeData.ARCHIVE_FOREGROUND_COLOR, createColor(attributes));
            else if(state == STATE_ARCHIVE_SELECTED)
                template.setColor(ThemeData.ARCHIVE_SELECTED_FOREGROUND_COLOR, createColor(attributes));

            else if(state == STATE_SYMLINK_NORMAL)
                template.setColor(ThemeData.SYMLINK_FOREGROUND_COLOR, createColor(attributes));
            else if(state == STATE_SYMLINK_SELECTED)
                template.setColor(ThemeData.SYMLINK_SELECTED_FOREGROUND_COLOR, createColor(attributes));

            else if(state == STATE_MARKED_NORMAL)
                template.setColor(ThemeData.MARKED_FOREGROUND_COLOR, createColor(attributes));
            else if(state == STATE_MARKED_SELECTED)
                template.setColor(ThemeData.MARKED_SELECTED_FOREGROUND_COLOR, createColor(attributes));

            else if(state == STATE_FILE_NORMAL)
                template.setColor(ThemeData.FILE_FOREGROUND_COLOR, createColor(attributes));
            else if(state == STATE_FILE_SELECTED)
                template.setColor(ThemeData.FILE_SELECTED_FOREGROUND_COLOR, createColor(attributes));

            else if(state == STATE_SHELL_NORMAL)
                template.setColor(ThemeData.SHELL_FOREGROUND_COLOR, createColor(attributes));
            else if(state == STATE_SHELL_SELECTED)
                template.setColor(ThemeData.SHELL_SELECTED_FOREGROUND_COLOR, createColor(attributes));

            else if(state == STATE_SHELL_HISTORY_NORMAL)
                template.setColor(ThemeData.SHELL_HISTORY_FOREGROUND_COLOR, createColor(attributes));
            else if(state == STATE_SHELL_HISTORY_SELECTED)
                template.setColor(ThemeData.SHELL_HISTORY_SELECTED_FOREGROUND_COLOR, createColor(attributes));

            else if(state == STATE_EDITOR_NORMAL)
                template.setColor(ThemeData.EDITOR_FOREGROUND_COLOR, createColor(attributes));
            else if(state == STATE_EDITOR_SELECTED)
                template.setColor(ThemeData.EDITOR_SELECTED_FOREGROUND_COLOR, createColor(attributes));

            else if(state == STATE_LOCATION_BAR_NORMAL)
                template.setColor(ThemeData.LOCATION_BAR_FOREGROUND_COLOR, createColor(attributes));
            else if(state == STATE_LOCATION_BAR_SELECTED)
                template.setColor(ThemeData.LOCATION_BAR_SELECTED_FOREGROUND_COLOR, createColor(attributes));

            else if(state == STATE_STATUS_BAR)
                template.setColor(ThemeData.STATUS_BAR_FOREGROUND_COLOR, createColor(attributes));
            
            else if(state == STATE_QUICK_LIST_HEADER)
            	template.setColor(ThemeData.QUICK_LIST_HEADER_FOREGROUND_COLOR, createColor(attributes));
            else if(state == STATE_QUICK_LIST_ITEM_NORMAL)
            	template.setColor(ThemeData.QUICK_LIST_ITEM_FOREGROUND_COLOR, createColor(attributes));
            else if(state == STATE_QUICK_LIST_ITEM_SELECTED)
            	template.setColor(ThemeData.QUICK_LIST_SELECTED_ITEM_FOREGROUND_COLOR, createColor(attributes));

            else
                traceIllegalDeclaration(qName);
        }

        else
            traceIllegalDeclaration(qName);
    }

    /**
     * Notifies the reader that the current element declaration is over.
     */
    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        // If we're in an unknown element....
        if(unknownElement != null) {
            // If it just closed, resume normal parsing.
            if(qName.equals(unknownElement))
                unknownElement = null;
            // Ignores all other tags.
            else
                return;
        }

        // XML root element.
        if(qName.equals(ELEMENT_ROOT))
            state = STATE_UNKNOWN;

        // File table declaration.
        else if(qName.equals(ELEMENT_TABLE))
            state = STATE_ROOT;

        else if(qName.equals(ELEMENT_ALTERNATE))
            state = STATE_TABLE;

        else if(qName.equals(ELEMENT_UNMATCHED))
            state = STATE_TABLE;

        else if(qName.equals(ELEMENT_HIDDEN))
            state = STATE_TABLE;

        else if(qName.equals(ELEMENT_FOLDER))
            state = STATE_TABLE;

        else if(qName.equals(ELEMENT_ARCHIVE))
            state = STATE_TABLE;

        else if(qName.equals(ELEMENT_SYMLINK))
            state = STATE_TABLE;

        else if(qName.equals(ELEMENT_MARKED))
            state = STATE_TABLE;

        else if(qName.equals(ELEMENT_FILE))
            state = STATE_TABLE;

        // Shell declaration.
        else if(qName.equals(ELEMENT_SHELL))
            state = STATE_ROOT;

        // Shell history declaration.
        else if(qName.equals(ELEMENT_SHELL_HISTORY))
            state = STATE_ROOT;

        // Editor declaration.
        else if(qName.equals(ELEMENT_EDITOR))
            state = STATE_ROOT;

        // Location bar declaration.
        else if(qName.equals(ELEMENT_LOCATION_BAR))
            state = STATE_ROOT;
        
        // Quick list declaration.
        else if(qName.equals(ELEMENT_QUICK_LIST))
            state = STATE_ROOT;

        // Volume label declaration
        else if(qName.equals(ELEMENT_STATUS_BAR))
            state = STATE_ROOT;
        
        // Header declaration.
        else if(qName.equals(ELEMENT_HEADER)) {
        	if(state == STATE_QUICK_LIST_HEADER)
                state = STATE_QUICK_LIST;
        }
        
        // Item declaration.
        else if(qName.equals(ELEMENT_ITEM)) {
        	if(state == STATE_QUICK_LIST_ITEM)
                state = STATE_QUICK_LIST;
        }

        // Normal element declaration.
        else if(qName.equals(ELEMENT_NORMAL)) {
            if(state == STATE_SHELL_NORMAL)
                state = STATE_SHELL;
            else if(state == STATE_SHELL_HISTORY_NORMAL)
                state = STATE_SHELL_HISTORY;
            else if(state == STATE_HIDDEN_NORMAL)
                state = STATE_HIDDEN;
            else if(state == STATE_FOLDER_NORMAL)
                state = STATE_FOLDER;
            else if(state == STATE_ARCHIVE_NORMAL)
                state = STATE_ARCHIVE;
            else if(state == STATE_SYMLINK_NORMAL)
                state = STATE_SYMLINK;
            else if(state == STATE_MARKED_NORMAL)
                state = STATE_MARKED;
            else if(state == STATE_FILE_NORMAL)
                state = STATE_FILE;
            else if(state == STATE_EDITOR_NORMAL)
                state = STATE_EDITOR;
            else if(state == STATE_LOCATION_BAR_NORMAL)
                state = STATE_LOCATION_BAR;
            else if(state == STATE_TABLE_NORMAL)
                state = STATE_TABLE;
            else if(state == STATE_QUICK_LIST_ITEM_NORMAL)
            	state = STATE_QUICK_LIST_ITEM;
        }

        // Selected element declaration.
        else if(qName.equals(ELEMENT_SELECTED)) {
            if(state == STATE_SHELL_SELECTED)
                state = STATE_SHELL;
            else if(state == STATE_SHELL_HISTORY_SELECTED)
                state = STATE_SHELL_HISTORY;
            else if(state == STATE_HIDDEN_SELECTED)
                state = STATE_HIDDEN;
            else if(state == STATE_FOLDER_SELECTED)
                state = STATE_FOLDER;
            else if(state == STATE_ARCHIVE_SELECTED)
                state = STATE_ARCHIVE;
            else if(state == STATE_SYMLINK_SELECTED)
                state = STATE_SYMLINK;
            else if(state == STATE_MARKED_SELECTED)
                state = STATE_MARKED;
            else if(state == STATE_FILE_SELECTED)
                state = STATE_FILE;
            else if(state == STATE_EDITOR_SELECTED)
                state = STATE_EDITOR;
            else if(state == STATE_LOCATION_BAR_SELECTED)
                state = STATE_LOCATION_BAR;
            else if(state == STATE_TABLE_SELECTED)
                state = STATE_TABLE;
            else if(state == STATE_QUICK_LIST_ITEM_SELECTED)
            	state = STATE_QUICK_LIST_ITEM;
        }
    }



    // - Helper methods ------------------------------------------------------
    // -----------------------------------------------------------------------
    /**
     * Checks whether the specified font is available on the system.
     * @param  font name of the font to check for.
     * @return <code>true</code> if the font is available, <code>false</code> otherwise.
     */
    private static boolean isFontAvailable(String font)  {
	String[] availableFonts; // All available fonts.

	// Looks for the specified font.
	availableFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
    for (String availableFont : availableFonts)
        if (availableFont.equalsIgnoreCase(font))
            return true;

	// Font doesn't exist on the system.
	return false;
    }

    /**
     * Creates a font from the specified XML attributes.
     * <p>
     * Ignored attributes will be set to their default values.
     * </p>
     * @param  attributes XML attributes describing the font to use.
     * @return            the resulting Font instance.
     */
    private static Font createFont(Attributes attributes) {
        String          buffer; // Buffer for attribute values.
        int             size;   // Font size.
        int             style;  // Font style.
        StringTokenizer parser; // Used to parse the font family.

        // Computes the font style.
        style = 0;
        if(((buffer = attributes.getValue(ATTRIBUTE_BOLD)) != null) && buffer.equals(VALUE_TRUE))
            style |= Font.BOLD;
        if(((buffer = attributes.getValue(ATTRIBUTE_ITALIC)) != null) && buffer.equals(VALUE_TRUE))
            style |= Font.ITALIC;

        // Computes the font size.
        if((buffer = attributes.getValue(ATTRIBUTE_SIZE)) == null) {
            LOGGER.debug("Missing font size attribute in theme, ignoring.");
            return null;
	    }
        size = Integer.parseInt(buffer);

            // Computes the font family.
            if((buffer = attributes.getValue(ATTRIBUTE_FAMILY)) == null) {
                LOGGER.debug("Missing font family attribute in theme, ignoring.");
                return null;
        }

        // Looks through the list of declared fonts to find one that is installed on the system.
        parser = new StringTokenizer(buffer, ",");
        while(parser.hasMoreTokens()) {
            buffer = parser.nextToken().trim();

            // Font was found, use it.
            if(isFontAvailable(buffer))
            return new Font(buffer, style, size);
        }

        // No font was found, instructs the ThemeManager to use the system default.
        LOGGER.debug("Requested font families are not installed on the system, using default.");
        return null;
    }

    /**
     * Creates a color from the specified XML attributes.
     * @param  attributes XML attributes describing the font to use.
     * @return            the resulting Color instance.
     */
    private static Color createColor(Attributes attributes) {
        String buffer;
        int    color;

        // Retrieves the color attribute's value.
        if((buffer = attributes.getValue(ATTRIBUTE_COLOR)) == null) {
            LOGGER.debug("Missing color attribute in theme, ignoring.");
            return null;
        }
        color = Integer.parseInt(buffer, 16);

        // Retrieves the transparency attribute's value..
        if((buffer = attributes.getValue(ATTRIBUTE_ALPHA)) == null)
            return new Color(color);
        return new Color(color | (Integer.parseInt(buffer, 16) << 24), true);
    }


    // - Error generation methods --------------------------------------------
    // -----------------------------------------------------------------------
    private void traceIllegalDeclaration(String element) {
        unknownElement = element;
        LOGGER.debug("Unexpected start of element " + element + ", ignoring.");
    }
}
