/*
 * Copyright 2010-2015 Institut Pasteur.
 * 
 * This file is part of Icy.
 * 
 * Icy 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.
 * 
 * Icy 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 Icy. If not, see <http://www.gnu.org/licenses/>.
 */
package icy.sequence;

import icy.file.FileUtil;
import icy.file.xml.XMLPersistent;
import icy.image.lut.LUT;
import icy.painter.Overlay;
import icy.roi.ROI;
import icy.system.IcyExceptionHandler;
import icy.util.StringUtil;
import icy.util.XMLUtil;

import java.util.LinkedList;
import java.util.List;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
 * @author Stephane
 */
public class SequencePersistent implements XMLPersistent
{
    private final static String ID_META = "meta";
    private final static String ID_ROIS = "rois";
    private final static String ID_OVERLAYS = "overlays";
    private final static String ID_LUT = "lut";

    private final Sequence sequence;

    private Document document;

    /**
     * 
     */
    public SequencePersistent(Sequence sequence)
    {
        super();

        this.sequence = sequence;

        document = XMLUtil.createDocument(true);
    }

    /**
     * Should return <code>null</code> if Sequence is not identified (no file name)
     */
    private String getXMLFileName()
    {
        final String baseName = sequence.getOutputBaseName("meta");

        if (StringUtil.isEmpty(baseName))
            return null;

        return baseName + sequence.getOutputExtension() + XMLUtil.FILE_DOT_EXTENSION;
    }

    /**
     * Load XML persistent data.<br>
     * Return true if XML data has been correctly loaded.
     */
    public boolean loadXMLData()
    {
        final String xmlFilename = getXMLFileName();
        boolean result;
        Exception exc = null;

        if ((xmlFilename != null) && FileUtil.exists(xmlFilename))
        {
            try
            {
                // load xml file into document
                document = XMLUtil.loadDocument(xmlFilename, true);

                // load data from XML document
                if (document != null)
                    result = loadFromXML(getRootNode());
                else
                {
                    document = XMLUtil.createDocument(true);
                    result = false;
                }
            }
            catch (Exception e)
            {
                exc = e;
                result = false;
            }

            // an error occurred
            if (!result)
            {
                // backup the problematic file
                String backupName = FileUtil.backup(xmlFilename);

                System.err.println("Error while loading Sequence XML persistent data.");
                System.err.println("The faulty file '" + xmlFilename + "' has been backuped as '" + backupName);

                if (exc != null)
                    IcyExceptionHandler.showErrorMessage(exc, true);

                return false;
            }
        }

        return true;
    }

    /**
     * Save XML persistent data.<br>
     * Return true if XML data has been correctly saved.
     */
    public boolean saveXMLData() throws Exception
    {
        final String xmlFilename = getXMLFileName();

        if (xmlFilename == null)
            return false;

        // rebuild document
        refreshXMLData();

        // save xml file
        return XMLUtil.saveDocument(document, xmlFilename);
    }

    public void refreshXMLData()
    {
        // force the new format when we save the XML
        saveToXML(getRootNode());
    }

    @Override
    public boolean loadFromXML(Node node)
    {
        boolean result = true;
        final String name = XMLUtil.getElementValue(node, Sequence.ID_NAME, "");

        // set name only if not empty
        if (!StringUtil.isEmpty(name))
            sequence.setName(name);

        if (!loadMetaDataFromXML(node))
            result = false;
        if (!loadROIsFromXML(node))
            result = false;
        // some overlays does not support persistence so we can ignore errors...
        loadOverlaysFromXML(node);
        if (!loadLUTFromXML(node))
            result = false;

        return result;
    }

    private boolean loadMetaDataFromXML(Node node)
    {
        final Node nodeMeta = XMLUtil.getElement(node, ID_META);

        // no node --> nothing to load...
        if (nodeMeta == null)
            return true;

        sequence.setPositionX(XMLUtil.getElementDoubleValue(nodeMeta, Sequence.ID_POSITION_X, 0d));
        sequence.setPositionY(XMLUtil.getElementDoubleValue(nodeMeta, Sequence.ID_POSITION_Y, 0d));
        sequence.setPositionZ(XMLUtil.getElementDoubleValue(nodeMeta, Sequence.ID_POSITION_Z, 0d));
        sequence.setPixelSizeX(XMLUtil.getElementDoubleValue(nodeMeta, Sequence.ID_PIXEL_SIZE_X, 1d));
        sequence.setPixelSizeY(XMLUtil.getElementDoubleValue(nodeMeta, Sequence.ID_PIXEL_SIZE_Y, 1d));
        sequence.setPixelSizeZ(XMLUtil.getElementDoubleValue(nodeMeta, Sequence.ID_PIXEL_SIZE_Z, 1d));
        sequence.setTimeInterval(XMLUtil.getElementDoubleValue(nodeMeta, Sequence.ID_TIME_INTERVAL, 1d));

        for (int c = 0; c < sequence.getSizeC(); c++)
            sequence.setChannelName(
                    c,
                    XMLUtil.getElementValue(nodeMeta, Sequence.ID_CHANNEL_NAME + c, MetaDataUtil.DEFAULT_CHANNEL_NAME
                            + c));

        return true;
    }

    private boolean loadROIsFromXML(Node node)
    {
        final Node roisNode = XMLUtil.getElement(node, ID_ROIS);

        // no node --> nothing to load...
        if (roisNode == null)
            return true;

        final int roiCount = ROI.getROICount(roisNode);
        final List<ROI> rois = ROI.loadROIsFromXML(roisNode);

        // add to sequence
        for (ROI roi : rois)
            sequence.addROI(roi);

        // return true if we got the expected number of ROI
        return (roiCount == rois.size());
    }

    private boolean loadOverlaysFromXML(Node node)
    {
        final Node overlaysNode = XMLUtil.getElement(node, ID_OVERLAYS);

        // no node --> nothing to load...
        if (overlaysNode == null)
            return true;

        final int overlayCount = Overlay.getOverlayCount(overlaysNode);
        final List<Overlay> overlays = Overlay.loadOverlaysFromXML(overlaysNode);

        // add to sequence
        for (Overlay overlay : overlays)
            sequence.addOverlay(overlay);

        // return true if we got the expected number of ROI
        return (overlayCount == overlays.size());
    }

    private boolean loadLUTFromXML(Node node)
    {
        final Node nodeLut = XMLUtil.getElement(node, ID_LUT);

        // no node --> nothing to load...
        if (nodeLut == null)
            return true;

        // use the default LUT by default
        final LUT result = sequence.getDefaultLUT();

        if (result.loadFromXML(nodeLut))
            sequence.setUserLUT(result);

        return true;
    }

    @Override
    public boolean saveToXML(Node node)
    {
        XMLUtil.setElementValue(node, Sequence.ID_NAME, sequence.getName());

        saveMetaDataToXML(node);
        saveROIsToXML(node);
        saveOverlaysToXML(node);
        saveLUTToXML(node);

        return true;
    }

    private void saveMetaDataToXML(Node node)
    {
        final Node nodeMeta = XMLUtil.setElement(node, ID_META);

        if (nodeMeta != null)
        {
            XMLUtil.setElementDoubleValue(nodeMeta, Sequence.ID_POSITION_X, sequence.getPositionX());
            XMLUtil.setElementDoubleValue(nodeMeta, Sequence.ID_POSITION_Y, sequence.getPositionY());
            XMLUtil.setElementDoubleValue(nodeMeta, Sequence.ID_POSITION_Z, sequence.getPositionZ());
            XMLUtil.setElementDoubleValue(nodeMeta, Sequence.ID_PIXEL_SIZE_X, sequence.getPixelSizeX());
            XMLUtil.setElementDoubleValue(nodeMeta, Sequence.ID_PIXEL_SIZE_Y, sequence.getPixelSizeY());
            XMLUtil.setElementDoubleValue(nodeMeta, Sequence.ID_PIXEL_SIZE_Z, sequence.getPixelSizeZ());
            XMLUtil.setElementDoubleValue(nodeMeta, Sequence.ID_TIME_INTERVAL, sequence.getTimeInterval());

            for (int c = 0; c < sequence.getSizeC(); c++)
                XMLUtil.setElementValue(nodeMeta, Sequence.ID_CHANNEL_NAME + c, sequence.getChannelName(c));
        }
    }

    private void saveROIsToXML(Node node)
    {
        final Node nodeROIs = XMLUtil.setElement(node, ID_ROIS);

        if (nodeROIs != null)
        {
            XMLUtil.removeAllChildren(nodeROIs);

            // get sorted ROIs
            final List<ROI> rois = sequence.getROIs(true);

            // set rois in the XML node
            ROI.saveROIsToXML(nodeROIs, rois);
        }
    }

    private void saveOverlaysToXML(Node node)
    {
        final Node nodeOverlays = XMLUtil.setElement(node, ID_OVERLAYS);

        if (nodeOverlays != null)
        {
            XMLUtil.removeAllChildren(nodeOverlays);

            // get overlays in linked list for faster remove operation
            final List<Overlay> overlays = new LinkedList<Overlay>(sequence.getOverlays());
            // remove overlays from ROI as they are be automatically created from ROI
            for (ROI roi : sequence.getROIs(false))
                overlays.remove(roi.getOverlay());

            // set overlays in the XML node
            Overlay.saveOverlaysToXML(nodeOverlays, overlays);
        }
    }

    private void saveLUTToXML(Node node)
    {
        // save only if we have a custom LUT
        if (sequence.hasUserLUT())
        {
            final LUT lut = sequence.getUserLUT();

            // something to save ?
            if (lut != null)
            {
                final Node nodeLut = XMLUtil.setElement(node, ID_LUT);

                if (nodeLut != null)
                {
                    XMLUtil.removeAllChildren(nodeLut);
                    lut.saveToXML(nodeLut);
                }
            }
        }
    }

    /**
     * Get Sequence XML root node
     */
    public Node getRootNode()
    {
        return XMLUtil.getRootElement(document);
    }

    /**
     * Get XML data node identified by specified name
     * 
     * @param name
     *        name of wanted node
     */
    public Node getNode(String name)
    {
        return XMLUtil.getChild(getRootNode(), name);
    }

    /**
     * Create a new node with specified name and return it.<br>
     * If the node already exists the existing node is returned.
     * 
     * @param name
     *        name of node to set in attached XML data
     */
    public Node setNode(String name)
    {
        return XMLUtil.setElement(getRootNode(), name);
    }

    /**
     * Returns <code>true</code> if the specified Document represents a valid XML persistence document.
     */
    public static boolean isValidXMLPersitence(Document doc)
    {
        if (doc == null)
            return false;

        final Element rootNode = XMLUtil.getRootElement(doc);

        return (XMLUtil.getElement(rootNode, Sequence.ID_NAME) != null)
                && (XMLUtil.getElement(rootNode, ID_META) != null) && (XMLUtil.getElement(rootNode, ID_ROIS) != null)
                && (XMLUtil.getElement(rootNode, ID_OVERLAYS) != null);
    }

    /**
     * Returns <code>true</code> if the specified path represents a valid XML persistence file.
     */
    public static boolean isValidXMLPersitence(String path)
    {
        if ((path != null) && FileUtil.exists(path))
        {
            try
            {
                return isValidXMLPersitence(XMLUtil.loadDocument(path, true));
            }
            catch (Exception e)
            {
                // ignore
            }
        }

        return false;
    }
}