/*
 * Copyright (c) 2005 FlexDock Development Team. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in the
 * Software without restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
 * to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE.
 */
package org.flexdock.perspective.persist.xml;

import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.flexdock.docking.state.DockingPath;
import org.flexdock.docking.state.DockingState;
import org.flexdock.docking.state.FloatingGroup;
import org.flexdock.docking.state.LayoutNode;
import org.flexdock.docking.state.PersistenceException;
import org.flexdock.docking.state.tree.DockableNode;
import org.flexdock.docking.state.tree.DockingPortNode;
import org.flexdock.docking.state.tree.SplitNode;
import org.flexdock.perspective.Layout;
import org.flexdock.perspective.LayoutSequence;
import org.flexdock.perspective.Perspective;
import org.flexdock.perspective.persist.Persister;
import org.flexdock.perspective.persist.PerspectiveModel;
import org.flexdock.util.Utilities;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Created on 2005-06-03
 *
 * @author <a href="mailto:mati@sz.home.pl">Mateusz Szczap</a>
 * @version $Id: XMLPersister.java,v 1.23 2006-12-20 20:55:21 kschaefe Exp $
 */
public class XMLPersister implements Persister {

    /**
     * {@inheritDoc}
     */
    public boolean store(OutputStream os, PerspectiveModel perspectiveModel) throws IOException, PersistenceException {
        DocumentBuilder documentBuilder = createDocumentBuilder();
        Document document = documentBuilder.newDocument();

        ISerializer perspectiveModelSerializer = SerializerRegistry.getSerializer(PerspectiveModel.class);
        Element perspectiveModelElement = perspectiveModelSerializer.serialize(document, perspectiveModel);

        document.appendChild(perspectiveModelElement);

        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        // the indent-number attribute causes an IllegalArgumentException under 1.4
        if(Utilities.JAVA_1_5) {
            transformerFactory.setAttribute("indent-number", new Integer(4));
        }

        try {
            Transformer transformer = transformerFactory.newTransformer();
            // this property is ignored under java 1.5.
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");

            DOMSource source = new DOMSource(document);
            StreamResult result = new StreamResult(new OutputStreamWriter(os));

            transformer.transform(source, result);
        } catch (TransformerConfigurationException ex) {
            throw new PersistenceException("Unable to serialize perspectiveModel", ex);
        } catch (TransformerException ex) {
            throw new PersistenceException("Unable to serialize perspectiveModel", ex);
        }

        return true;
    }

    /**
     * {@inheritDoc}
     */
    public PerspectiveModel load(InputStream is) throws IOException, PersistenceException {
        try {
            InputSource inputSource = new InputSource(is);
            DocumentBuilder documentBuilder = createDocumentBuilder();
            Document document = documentBuilder.parse(inputSource);

            ISerializer perspectiveModelSerializer = SerializerRegistry.getSerializer(PerspectiveModel.class);
            NodeList perspectiveModelNodeList = document.getElementsByTagName(PersistenceConstants.PERSPECTIVE_MODEL_ELEMENT_NAME);
            if (perspectiveModelNodeList.getLength() > 0 && perspectiveModelNodeList.item(0) instanceof Element) {
                Element perspectiveModelElement = (Element) perspectiveModelNodeList.item(0);
                return (PerspectiveModel) perspectiveModelSerializer.deserialize(perspectiveModelElement);
            }

            return null;
        } catch (SAXException ex) {
            throw new PersistenceException("Unable to deserialize perspectiveModel from xml", ex);
        }
    }

    private void registerSerializers() {
        SerializerRegistry.registerSerializer(Perspective.class, new PerspectiveSerializer());
        SerializerRegistry.registerSerializer(Layout.class, new LayoutSerializer());
        SerializerRegistry.registerSerializer(LayoutSequence.class, new LayoutSequenceSerializer());
        SerializerRegistry.registerSerializer(DockingState.class, new DockingStateSerializer());
        SerializerRegistry.registerSerializer(Point.class, new PointSerializer());
        SerializerRegistry.registerSerializer(Dimension.class, new DimensionSerializer());
        SerializerRegistry.registerSerializer(Rectangle.class, new RectangleSerializer());
        SerializerRegistry.registerSerializer(FloatingGroup.class, new FloatingGroupSerializer());
        SerializerRegistry.registerSerializer(DockingPath.class, new DockingPathSerializer());
        SerializerRegistry.registerSerializer(PerspectiveModel.class, new PerspectiveModelSerializer());
        SerializerRegistry.registerSerializer(LayoutNode.class, new LayoutNodeSerializer());
        SerializerRegistry.registerSerializer(SplitNode.class, new SplitNodeSerializer());
        SerializerRegistry.registerSerializer(DockingPortNode.class, new DockingPortNodeSerializer());
        SerializerRegistry.registerSerializer(DockableNode.class, new DockableNodeSerializer());
    }

    public static XMLPersister newDefaultInstance() {
        XMLPersister persister = new XMLPersister();
        persister.registerSerializers();

        return persister;
    }

    private DocumentBuilder createDocumentBuilder() throws PersistenceException {
        try {
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();

            return documentBuilder;
        } catch (ParserConfigurationException ex) {
            throw new PersistenceException("Unable to create document builder", ex);
        }
    }

}
