/*
 * Copyright (C) 2013 Robert Simonovsky
 *
 * This program 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.
 *
 * This program 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 cz.cas.lib.proarc.common.export.desa.structure;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

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

import com.yourmediashelf.fedora.generated.foxml.DatastreamVersionType;
import com.yourmediashelf.fedora.generated.foxml.DigitalObject;

import cz.cas.lib.proarc.common.export.desa.Const;
import cz.cas.lib.proarc.common.export.desa.DesaContext;
import cz.cas.lib.proarc.common.export.mets.MetsExportException;
import cz.cas.lib.proarc.common.export.mets.MetsUtils;
import cz.cas.lib.proarc.common.fedora.FoxmlUtils;

/**
 * Class that represents the element of Desa export
 *
 * @author Robert Simonovsky
 *
 */
public class DesaElement implements IDesaElement {
    public final List<Element> descriptor;
    public final String model;
    private final DesaContext desaContext;
    private final String originalPid;
    private final Logger LOG = Logger.getLogger(DesaElement.class.getName());
    private DesaElement parent;
    private final List<DesaElement> children = new ArrayList<DesaElement>();
    private final List<Element> relsExt;
    private final DigitalObject sourceObject;
    private String idSIPVersion;
    private final String descriptorType;

    /*
     * (non-Javadoc)
     *
     * @see
     * cz.cas.lib.proarc.common.export.desa.structure.IDesaElement#getDescriptorType
     * ()
     */
    @Override
    public String getDescriptorType() {
        return descriptorType;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * cz.cas.lib.proarc.common.export.desa.structure.IDesaElement#getIdSIPVersion
     * ()
     */
    @Override
    public String getIdSIPVersion() {
        return idSIPVersion;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * cz.cas.lib.proarc.common.export.desa.structure.IDesaElement#setIdSIPVersion
     * (java.lang.String)
     */
    @Override
    public void setIdSIPVersion(String idSIPVersion) {
        this.idSIPVersion = idSIPVersion;
    }

    private final String elementType;
    private final String elementID;
    private String zipName;

    /*
     * (non-Javadoc)
     *
     * @see
     * cz.cas.lib.proarc.common.export.desa.structure.IDesaElement#getZipName()
     */
    @Override
    public String getZipName() {
        return zipName;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * cz.cas.lib.proarc.common.export.desa.structure.IDesaElement#setZipName
     * (java.lang.String)
     */
    @Override
    public void setZipName(String zipName) throws MetsExportException {
        this.zipName = zipName;
        if (!checkZipFileOK()) {
            throw new MetsExportException(this.getOriginalPid(), "Identifier is not valid - generated output file name is:" + zipName, false, null);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * cz.cas.lib.proarc.common.export.desa.structure.IDesaElement#getDesaContext
     * ()
     */
    @Override
    public DesaContext getDesaContext() {
        return desaContext;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * cz.cas.lib.proarc.common.export.desa.structure.IDesaElement#getElementType
     * ()
     */
    @Override
    public String getElementType() {
        return elementType;
    }

    /**
     * Constructor
     *
     * @param digitalObject
     * @param parent
     * @param desaContext
     * @param fillChildren
     * @throws MetsExportException
     */
    public DesaElement(DigitalObject digitalObject, Object parent, DesaContext desaContext, boolean fillChildren) throws MetsExportException {
        this.desaContext = desaContext;
        this.sourceObject = digitalObject;
        this.relsExt = FoxmlUtils.findDatastream(digitalObject, "RELS-EXT").getDatastreamVersion().get(0).getXmlContent().getAny();
        DatastreamVersionType biblioVersion = FoxmlUtils.findDatastream(digitalObject, "BIBLIO_MODS").getDatastreamVersion().get(0);
        this.descriptor = biblioVersion.getXmlContent().getAny();
        this.descriptorType = biblioVersion.getFORMATURI();
        model = MetsUtils.getModel(relsExt);
        this.elementType = Const.typeMap.get(model);
        this.elementID = this.elementType + desaContext.addElementId(this.elementType);
        this.originalPid = digitalObject.getPID();
        if (parent instanceof DesaElement) {
            this.parent = (DesaElement) parent;
        }
        if (fillChildren) {
            fillChildren();
        }
        if (parent == null) {
            if (Const.FILE.equals(this.getElementType())) {
                this.parent = initParent();
            }
        }

        if (this.parent == null) {
            desaContext.setRootElement(this);
            LOG.log(Level.FINE, "Root element found:" + getOriginalPid() + "(" + getElementType() + ")");
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * cz.cas.lib.proarc.common.export.desa.structure.IDesaElement#getElementID
     * ()
     */
    @Override
    public String getElementID() {
        return elementID;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * cz.cas.lib.proarc.common.export.desa.structure.IDesaElement#getParent()
     */
    @Override
    public DesaElement getParent() {
        return parent;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * cz.cas.lib.proarc.common.export.desa.structure.IDesaElement#getChildren()
     */
    @Override
    public List<DesaElement> getChildren() {
        return children;
    }

    /**
     * Inits the parent element of current element
     *
     * @return
     * @throws MetsExportException
     */
    private DesaElement initParent() throws MetsExportException {
        String parentId;
        if (desaContext.getFedoraClient() != null) {
            parentId = MetsUtils.getParent(originalPid, desaContext.getRemoteStorage());
            LOG.fine("Parent found from Fedora:" + parentId);
        } else {
            parentId = MetsUtils.getParent(originalPid, desaContext.getFsParentMap());
            LOG.fine("Parent found from Local:" + parentId);
        }

        if (parentId == null) {
            LOG.fine("Parent not found - returning null");
            return null;
        }

        DigitalObject parentObject = null;
        if (desaContext.getFedoraClient() != null) {
            parentObject = MetsUtils.readRelatedFoXML(parentId, desaContext.getFedoraClient());
        } else {
            parentObject = MetsUtils.readRelatedFoXML(desaContext.getPath(), parentId);
        }
        DesaElement parentInit = new DesaElement(parentObject, null, desaContext, false);
        parentInit.children.add(this);
        return parentInit;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * cz.cas.lib.proarc.common.export.desa.structure.IDesaElement#getDescriptor
     * ()
     */
    @Override
    public List<Element> getDescriptor() {
        return descriptor;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * cz.cas.lib.proarc.common.export.desa.structure.IDesaElement#getModel()
     */
    @Override
    public String getModel() {
        return model;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * cz.cas.lib.proarc.common.export.desa.structure.IDesaElement#getOriginalPid
     * ()
     */
    @Override
    public String getOriginalPid() {
        return originalPid;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * cz.cas.lib.proarc.common.export.desa.structure.IDesaElement#getRelsExt()
     */
    @Override
    public List<Element> getRelsExt() {
        return relsExt;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * cz.cas.lib.proarc.common.export.desa.structure.IDesaElement#getSourceObject
     * ()
     */
    @Override
    public DigitalObject getSourceObject() {
        return sourceObject;
    }

    /**
     * Static method for instantiating an Element
     *
     * @param object
     * @param parent
     * @param desaContext
     * @param withChildren
     * @return
     * @throws MetsExportException
     */
    public static DesaElement getElement(DigitalObject object, DesaElement parent, DesaContext desaContext, boolean withChildren) throws MetsExportException {
        List<Element> relsExt = FoxmlUtils.findDatastream(object, "RELS-EXT").getDatastreamVersion().get(0).getXmlContent().getAny();
        String model = MetsUtils.getModel(relsExt);
        String type = Const.typeMap.get(model);
        if (type == null) {
            throw new MetsExportException(object.getPID(), "Unknown model:" + model, false, null);
        }
        return new DesaElement(object, parent, desaContext, withChildren);
    }

    /**
     * Generates children of this element
     *
     */
    @Override
    public void fillChildren() throws MetsExportException {
        Node node = MetsUtils.xPathEvaluateNode(relsExt, "*[local-name()='RDF']/*[local-name()='Description']");
        NodeList hasPageNodes = node.getChildNodes();
        for (int a = 0; a < hasPageNodes.getLength(); a++) {
            if (MetsUtils.hasReferenceXML(hasPageNodes.item(a).getNodeName())) {
                Node rdfResourceNode = hasPageNodes.item(a).getAttributes().getNamedItem("rdf:resource");
                String fileName = rdfResourceNode.getNodeValue();

                DigitalObject object = null;
                if (desaContext.getFedoraClient() != null) {
                    object = MetsUtils.readRelatedFoXML(fileName, desaContext.getFedoraClient());
                } else {
                    object = MetsUtils.readRelatedFoXML(desaContext.getPath(), fileName);
                }
                DesaElement child = new DesaElement(object, this, desaContext, true);
                this.children.add(child);
                LOG.log(Level.FINE, "Child found for:" + getOriginalPid() + "(" + getElementType() + ") - " + child.getOriginalPid() + "(" + child.getElementType() + ")");
            }
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * cz.cas.lib.proarc.common.export.desa.structure.IDesaElement#accept(cz
     * .cas.lib.proarc.common.export.desa.structure.IDesaElementVisitor)
     */
    @Override
    public void accept(IDesaElementVisitor desaVisitor) throws MetsExportException {
        desaVisitor.insertIntoMets(this, null);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * cz.cas.lib.proarc.common.export.desa.structure.IDesaElement#accept(cz
     * .cas.lib.proarc.common.export.desa.structure.IDesaElementVisitor)
     */
    @Override
    public void accept(IDesaElementVisitor desaVisitor, HashMap<String, String> desaProps) throws MetsExportException {
        LOG.info("Export visitor accepted for:" + this.getOriginalPid() + "(" + this.getElementType() + ")");
        desaVisitor.insertIntoMets(this, desaProps);
    }

    /**
     * Checks if fileName is correct
     *
     * @param folder
     */
    private boolean checkZipFileOK() {
        File file = null;
        try {
            file = new File(this.getDesaContext().getOutputPath() + File.separator + this.getZipName() + ".zip");
            LOG.fine("Checking if fileName is OK:" + file.getAbsolutePath());
            if (file.exists()) {
                LOG.log(Level.SEVERE, "File:" + file.getAbsolutePath() + " already exists !");
                return false;
            }
            file.createNewFile();
            file.delete();
            return true;
        } catch (IOException e) {
            LOG.log(Level.WARNING, "file:" + file.getAbsolutePath() + " is not valid filename", e);
            return false;
        }

    }
}