/*  Copyright (C) 2005-2007  The Chemistry Development Kit (CDK) project
 *                     2011  Dmitry Katsubo <dmitry.katsubo@gmail.com>
 *
 *  Contact: cdk-devel@lists.sourceforge.net
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2.1
 *  of the License, or (at your option) any later version.
 *  All we ask is that proper credit is given for our work, which includes
 *  - but is not limited to - adding the above copyright notice to the beginning
 *  of your source code files, and to any copyright notice that you may distribute
 *  with programs based on this work.
 *
 *  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 Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */
package org.openscience.cdk.libio.cml;

import java.util.Iterator;
import java.util.Map;

import org.openscience.cdk.exception.CDKException;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IAtomContainer;
import org.openscience.cdk.interfaces.IBond;
import org.openscience.cdk.interfaces.IChemObject;
import org.openscience.cdk.qsar.DescriptorSpecification;
import org.openscience.cdk.qsar.DescriptorValue;
import org.openscience.cdk.qsar.result.BooleanResult;
import org.openscience.cdk.qsar.result.DoubleArrayResult;
import org.openscience.cdk.qsar.result.DoubleResult;
import org.openscience.cdk.qsar.result.IDescriptorResult;
import org.openscience.cdk.qsar.result.IntegerArrayResult;
import org.openscience.cdk.qsar.result.IntegerResult;
import org.xmlcml.cml.element.CMLArray;
import org.xmlcml.cml.element.CMLMetadata;
import org.xmlcml.cml.element.CMLMetadataList;
import org.xmlcml.cml.element.CMLProperty;
import org.xmlcml.cml.element.CMLPropertyList;
import org.xmlcml.cml.element.CMLScalar;

import nu.xom.Attribute;
import nu.xom.Element;

/**
 * Customizer for the libio-cml Convertor to be able to export details for
 * QSAR descriptors calculated for Molecules.
 *
 * @author        egonw
 * @cdk.created   2005-05-04
 * @cdk.module    qsarcml
 * @cdk.githash
 * @cdk.require   java1.5+
 */
public class QSARCustomizer implements ICMLCustomizer {

    private final static String QSAR_NAMESPACE = "qsar";
    private final static String QSAR_URI       = "http://www.blueobelisk.org/ontologies/chemoinformatics-algorithms/";

    @Override
    public void customize(IBond bond, Object nodeToAdd) throws Exception {
        customizeIChemObject(bond, nodeToAdd);
    }

    @Override
    public void customize(IAtom atom, Object nodeToAdd) throws Exception {
        customizeIChemObject(atom, nodeToAdd);
    }

    @Override
    public void customize(IAtomContainer molecule, Object nodeToAdd) throws Exception {
        customizeIChemObject(molecule, nodeToAdd);
    }

    private Element createScalar(IDescriptorResult value) {
        Element scalar = null;
        if (value instanceof DoubleResult) {
            scalar = new CMLScalar();
            scalar.addAttribute(new Attribute("dataType", "xsd:double"));
            scalar.appendChild("" + ((DoubleResult) value).doubleValue());
        } else if (value instanceof IntegerResult) {
            scalar = new CMLScalar();
            scalar.addAttribute(new Attribute("dataType", "xsd:int"));
            scalar.appendChild("" + ((IntegerResult) value).intValue());
        } else if (value instanceof BooleanResult) {
            scalar = new CMLScalar();
            scalar.addAttribute(new Attribute("dataType", "xsd:boolean"));
            scalar.appendChild("" + ((BooleanResult) value).booleanValue());
        } else if (value instanceof IntegerArrayResult) {
            IntegerArrayResult result = (IntegerArrayResult) value;
            scalar = new CMLArray();
            scalar.addAttribute(new Attribute("dataType", "xsd:int"));
            scalar.addAttribute(new Attribute("size", "" + result.length()));
            StringBuffer buffer = new StringBuffer();
            for (int i = 0; i < result.length(); i++) {
                buffer.append(result.get(i) + " ");
            }
            scalar.appendChild(buffer.toString());
        } else if (value instanceof DoubleArrayResult) {
            DoubleArrayResult result = (DoubleArrayResult) value;
            scalar = new CMLArray();
            scalar.addAttribute(new Attribute("dataType", "xsd:double"));
            scalar.addAttribute(new Attribute("size", "" + result.length()));
            StringBuffer buffer = new StringBuffer();
            for (int i = 0; i < result.length(); i++) {
                buffer.append(result.get(i) + " ");
            }
            scalar.appendChild(buffer.toString());
        } else {
            // logger.error("Could not convert this object to a scalar element: ", value);
            scalar = new CMLScalar();
            scalar.appendChild(value.toString());
        }
        return scalar;
    }

    private void customizeIChemObject(IChemObject object, Object nodeToAdd) throws Exception {
        if (!(nodeToAdd instanceof Element)) throw new CDKException("NodeToAdd must be of type nu.xom.Element!");

        Element element = (Element) nodeToAdd;
        Map<Object, Object> props = object.getProperties();
        Iterator<Object> keys = props.keySet().iterator();
        Element propList = null;
        while (keys.hasNext()) {
            Object key = keys.next();
            if (key instanceof DescriptorSpecification) {
                DescriptorSpecification specs = (DescriptorSpecification) key;
                DescriptorValue value = (DescriptorValue) props.get(key);
                IDescriptorResult result = value.getValue();
                if (propList == null) {
                    propList = new CMLPropertyList();
                }
                Element property = new CMLProperty();
                // setup up the metadata list
                Element metadataList = new CMLMetadataList();
                metadataList.addNamespaceDeclaration(QSAR_NAMESPACE, QSAR_URI);
                property.addAttribute(new Attribute("convention", QSAR_NAMESPACE + ":" + "DescriptorValue"));
                String specsRef = specs.getSpecificationReference();
                if (specsRef.startsWith(QSAR_URI)) {
                    property.addNamespaceDeclaration(QSAR_NAMESPACE, QSAR_URI);
                }
                CMLMetadata metaData = new CMLMetadata();
                metaData.addAttribute(new Attribute("dictRef", QSAR_NAMESPACE + ":" + "specificationReference"));
                metaData.addAttribute(new Attribute("content", specsRef));
                metadataList.appendChild(metaData);
                metaData = new CMLMetadata();
                metaData.addAttribute(new Attribute("dictRef", QSAR_NAMESPACE + ":" + "implementationTitle"));
                metaData.addAttribute(new Attribute("content", specs.getImplementationTitle()));
                metadataList.appendChild(metaData);
                metaData = new CMLMetadata();
                metaData.addAttribute(new Attribute("dictRef", QSAR_NAMESPACE + ":" + "implementationIdentifier"));
                metaData.addAttribute(new Attribute("content", specs.getImplementationIdentifier()));
                metadataList.appendChild(metaData);
                metaData = new CMLMetadata();
                metaData.addAttribute(new Attribute("dictRef", QSAR_NAMESPACE + ":" + "implementationVendor"));
                metaData.addAttribute(new Attribute("content", specs.getImplementationVendor()));
                metadataList.appendChild(metaData);
                // add parameter setting to the metadata list
                Object[] params = value.getParameters();
                //                logger.debug("Value: " + value.getSpecification().getImplementationIdentifier());
                if (params != null && params.length > 0) {
                    String[] paramNames = value.getParameterNames();
                    Element paramSettings = new CMLMetadataList();
                    paramSettings.addAttribute(new Attribute("title", QSAR_NAMESPACE + ":" + "descriptorParameters"));
                    for (int i = 0; i < params.length; i++) {
                        Element paramSetting = new CMLMetadata();
                        String paramName = paramNames[i];
                        Object paramVal = params[i];
                        if (paramName == null) {
                            // logger.error("Parameter name was null! Cannot output to CML.");
                        } else if (paramVal == null) {
                            // logger.error("Parameter setting was null! Cannot output to CML. Problem param: " + paramName);
                        } else {
                            paramSetting.addAttribute(new Attribute("title", paramNames[i]));
                            paramSetting.addAttribute(new Attribute("content", params[i].toString()));
                            paramSettings.appendChild(paramSetting);
                        }
                    }
                    metadataList.appendChild(paramSettings);
                }
                property.appendChild(metadataList);
                Element scalar = this.createScalar(result);
                scalar.addAttribute(new Attribute("dictRef", specsRef));
                // add the actual descriptor value
                property.appendChild(scalar);
                propList.appendChild(property);
            } // else: disregard all other properties
        }
        if (propList != null) {
            element.appendChild(propList);
        }
    }

}
