/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 *   
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/* $Rev$ $Date$ */

#include "commonj/sdo/SDOXMLFileWriter.h"   // Include first to avoid libxml compile problems!
#include "commonj/sdo/SDOXMLStreamWriter.h" // Include first to avoid libxml compile problems!
#include "commonj/sdo/SDOXMLBufferWriter.h" // Include first to avoid libxml compile problems!
#include "commonj/sdo/SDOXSDFileWriter.h"
#include "commonj/sdo/SDOXSDStreamWriter.h"
#include "commonj/sdo/SDOXSDBufferWriter.h"
#include "commonj/sdo/XSDHelperImpl.h"
#include "commonj/sdo/XMLDocumentImpl.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <libxml/uri.h>
#include "commonj/sdo/SDOSchemaSAX2Parser.h"
#include "commonj/sdo/SDOSAX2Parser.h"
#include "commonj/sdo/XSDPropertyInfo.h"
#include "commonj/sdo/XSDTypeInfo.h"

#include "commonj/sdo/SDORuntimeException.h"
#include "commonj/sdo/DASProperty.h"
#include "commonj/sdo/Logging.h"
#include "commonj/sdo/Type.h"
#include "commonj/sdo/SDOUtils.h"

namespace commonj
{
    namespace sdo
    {
        using internal::XSDPropertyInfo;
        using internal::PropertyDefinitionImpl;
        using internal::TypeDefinitionImpl;
        using internal::XSDTypeInfo;
        using internal::TypeDefinitions;
        using internal::TypeDefinitionsImpl;
        using internal::XmlDasPropertyDefs;
        using internal::XMLDAS_TypeDefs;
        using internal::SDOUtils;
    
        /**  XSDHelperImpl
         *
         * XSDHelperImpl is the implementation of the abstract class
         * XSDHelper.
         * Provides the means of loading and saving XSD information 
         * from/to the metadata (Types and properties)
         */

        XSDHelperImpl::XSDHelperImpl(DataFactoryPtr df)
        {
            dataFactory = staticCast<DataFactoryImplPtr>(df);
            if (!dataFactory) 
            {
                dataFactory = staticCast<DataFactoryImplPtr>(DataFactory::getDataFactory());
            }
        }
        
        XSDHelperImpl::~XSDHelperImpl()
        {
            clearErrors();
        }
        
        /**  define builds types/properties from file or stream
         *
         * define/defineFile
         *
         * Populates the data factory with Types and Properties from the schema
         * Loads from file, stream or char* buffer.
         * The return value is the URI of the root Type
         *
         */

        const char* XSDHelperImpl::defineFile(const char* schema)
        {
            clearErrors();
            SDOSchemaSAX2Parser schemaParser(schemaInfo, this, parsedLocations, definedNamespaces);

            // Build URI allowing for Windows path
            xmlChar* uri = xmlCanonicPath((xmlChar*)schema);
            SDOXMLString schemaUri(uri);
            if (uri)
            {
                xmlFree(uri);
            }

            if (0 != schemaParser.parseURI(schemaUri, SDOXMLString()))
                return 0;
            defineTypes(schemaParser.getTypeDefinitions());
            return schemaInfo.getTargetNamespaceURI();
        }

        const char* XSDHelperImpl::defineFile(const SDOString& schema)
        {
            return defineFile(schema.c_str());
        }
        
        const char*  XSDHelperImpl::define(std::istream& schema)
        {
            SDOSchemaSAX2Parser schemaParser(schemaInfo, this, parsedLocations, definedNamespaces);
            clearErrors();
            schema  >> schemaParser;
            defineTypes(schemaParser.getTypeDefinitions());
            return schemaInfo.getTargetNamespaceURI();
        }
        
        const char*  XSDHelperImpl::define(const char* schema)
        {
            std::istringstream str(schema);
            SDOSchemaSAX2Parser schemaParser(schemaInfo, this, parsedLocations, definedNamespaces);
            clearErrors();
            str  >> schemaParser;
            defineTypes(schemaParser.getTypeDefinitions());
            return schemaInfo.getTargetNamespaceURI();
            return define(str);
        }
        const char*  XSDHelperImpl::define(const SDOString& schema)
        {
            std::istringstream str(schema);
            SDOSchemaSAX2Parser schemaParser(schemaInfo, this, parsedLocations, definedNamespaces);
            clearErrors();
            str  >> schemaParser;
            defineTypes(schemaParser.getTypeDefinitions());
            return schemaInfo.getTargetNamespaceURI();
            return define(str);
        }
        
        void XSDHelperImpl::newSubstitute(const char* entryName,
                                          PropertyDefinitionImpl& prop)
        {
            try 
            {
                const char* typeUri = prop.substituteUri.isNull() ? 
                            prop.typeUri : prop.substituteUri; 
                const TypeImpl& rootType = dynamic_cast<const TypeImpl&>(dataFactory->getType(typeUri, entryName));
                const std::vector<PropertyImplPtr>& pl = rootType.getPropertyListReference();

                for (std::vector<PropertyImplPtr>::const_iterator j = pl.begin();
                     j != pl.end();
                     j++)
                {
                    if (!(*j)->getType().isDataType()
                        && strcmp((*j)->getType().getURI(),SDOUtils::sdoURI))
                    {
                        // recurse the tree..
                       newSubstitute((*j)->getType().getName(), prop);

                       if (!strcmp((*j)->getName(), prop.substituteName))
                        {
                            dataFactory->setPropertySubstitute(rootType.getURI(),
                                                           rootType.getName(),
                                                        (*j)->getName(), 
                                                        prop.name,
                                                        typeUri,
                                                        prop.typeName);
                            XSDPropertyInfo* pi = (XSDPropertyInfo*)
                            ((DASProperty*) getRawPointer(*j))->getDASValue("XMLDAS::PropertyInfo");

                            if (pi)
                            {
                                PropertyDefinitionImpl& propdef = (PropertyDefinitionImpl&)pi->getPropertyDefinition();
                                propdef.substituteNames.push_back(prop.name);
                                propdef.substituteLocalNames.push_back(prop.localname);
                            }
                        }
                    }
                }
            }
            catch (const SDORuntimeException&)
            {
            }
        }

        void XSDHelperImpl::addSubstitutes(PropertyDefinitionImpl& prop,
                                            TypeDefinitionImpl& ty)
        {
            try 
            {
                const char* typeUri =  prop.substituteUri.isNull() ?
                                 ty.uri : prop.substituteUri;  
                const TypeImpl* rootType = dataFactory->findTypeImpl(typeUri,"RootType");
                if (rootType == 0) return;

                const std::vector<PropertyImplPtr>& pl = rootType->getPropertyListReference();

                for (std::vector<PropertyImplPtr>::const_iterator j = pl.begin();
                     j != pl.end();
                     j++)
                {
                   XSDPropertyInfo* pi = (XSDPropertyInfo*)
                      ((DASProperty*) getRawPointer(*j))->getDASValue("XMLDAS::PropertyInfo");

                    if (pi)
                    {
                        PropertyDefinitionImpl& propdef = (PropertyDefinitionImpl&)pi->getPropertyDefinition();
                        if (propdef.isSubstitute && propdef.substituteName.equals(prop.name))
                        {

                            LOGINFO_1(INFO,"XSDHelper adding substitute for property %s", (const char*)prop.name);

                            dataFactory->setPropertySubstitute(typeUri,ty.name,
                                       prop.name, propdef.name,
                                       (*j)->getType().getURI(),
                                       (*j)->getType().getName());
                            prop.substituteNames.push_back(propdef.name);
                            prop.substituteLocalNames.push_back(propdef.localname);
                        }
                    }
                }
            }
            catch (const SDORuntimeException&)
            {
            }
        }


        /**  defineTypes
         *
         * This method works through all the data gathered during parsing
         * and defines all the types using the data factory.
         */

        void XSDHelperImpl::defineTypes(TypeDefinitions& types)
        {
            defineTypes(types.getTypeDefinitions());
        }

        
        /**  defineTypes
         *
         * This method works through all the data gathered during parsing
         * and defines all the types using the data factory.
         */

        void XSDHelperImpl::defineTypes(TypeDefinitionsImpl& typedefs) 
        {
            XMLDAS_TypeDefs::iterator iter;
            for (iter=typedefs.types.begin(); iter != typedefs.types.end(); iter++)
            {
                TypeDefinitionImpl& ty = iter->second;

                dataFactory->addType((const char*)ty.uri, (const char*)ty.name, ty.isSequenced, 
                                     ty.isOpen, ty.isAbstract, ty.dataType, ty.isFromList);
                dataFactory->setDASValue(
                    (const char*)ty.uri, (const char*)ty.name,
                    "XMLDAS::TypeInfo",
                    new XSDTypeInfo(ty));

                // add aliases
                if (!ty.aliases.isNull())
                {
                  SDOXMLString al = ty.aliases;
                  SDOXMLString al1 = "";
                  int index;
                  while (!al.isNull() && !al.equals(""))
                  {
                    index = al.lastIndexOf(' ');
                    if (index < 0)
                    {
                      al1 = al;
                      al = "";
                    }
                    else
                    {
                      al1 = al.substring(index + 1);
                      al = al.substring(0,index);
                    }
                    if (!al1.isNull() && !al1.equals(""))
                    {
                      dataFactory->setAlias(
                          ty.uri,
                          ty.name,
                          (const char*)al1);
                    }
                  }
                }
            }
            for (iter=typedefs.types.begin(); iter != typedefs.types.end(); iter++)
            {
                TypeDefinitionImpl& ty = iter->second;
                if (!ty.parentTypeName.isNull())
                {
                  dataFactory->setBaseType(
                      (const char*)ty.uri,
                      (const char*)ty.name,
                      (const char*)ty.parentTypeUri,
                      (const char*)ty.parentTypeName,
                      ty.isRestriction);
                }
                
                XmlDasPropertyDefs::iterator propsIter;
                for (propsIter = ty.properties.begin(); propsIter != ty.properties.end(); propsIter++)
                {
                    PropertyDefinitionImpl& prop = *propsIter;
                    
                    // For a refence we need to determine the type from the
                    // global element declaration
                    if(prop.isReference)
                    {
                        
                        bool refFound = false;

                        if (prop.name.isNull())
                            prop.name = prop.typeName;

                        
                        XMLDAS_TypeDefs::iterator refTypeIter = 
                            typedefs.types.find(TypeDefinitionsImpl::getTypeQName(prop.typeUri, "RootType"));
                        if(refTypeIter != typedefs.types.end())
                        {

                            TypeDefinitionImpl& rootTy = refTypeIter->second;
                            
                            // find the property on the root type
                            XmlDasPropertyDefs::iterator refPropsIter;
                            for (refPropsIter = rootTy.properties.begin(); refPropsIter != rootTy.properties.end(); refPropsIter++)
                            {

                                if (refPropsIter->localname.equals(prop.typeName))
                                {
                                    prop.typeUri = refPropsIter->typeUri;
                                    prop.typeName = refPropsIter->typeName;
                                    refFound = true;
                                }
                            }
                        }
                        if (!refFound)
                        {

                                // Check if this type is already defined to the data factory
                          const TypeImpl* rootType = dataFactory->findTypeImpl(prop.typeUri, "RootType");

                          if (rootType) {
                            const std::vector<PropertyImplPtr> pl = 
                                rootType->getPropertyListReference();

                              for (std::vector<PropertyImplPtr>::const_iterator j = pl.begin();
                                   j != pl.end();
                                   j++)
                              {
                                    
                                 XSDPropertyInfo* pi = (XSDPropertyInfo*)
                                    ((DASProperty*) getRawPointer(*j))->getDASValue("XMLDAS::PropertyInfo");
                                    
                                 if (prop.typeName.equals((*j)->getName())
                                     || (pi && prop.typeName.equals(pi->getPropertyDefinition().localname)))
                                 {
                                    prop.typeUri = (*j)->getType().getURI();
                                    prop.typeName = (*j)->getType().getName();
                                    refFound = true;
                                    break;
                                 }
                              }
                           }
                        }
                        // If we haven't been able to resolve this reference we should ignore it
                        if (!refFound)
                        {
                            LOGINFO_1(INFO,"XsdHelper:no matching reference for %s",
                                (const char *)prop.name);
                            continue;
                        }
                    }
                    
                    if (prop.name.isNull())
                    {
                        continue;
                    }
                    XMLDAS_TypeDefs::iterator propTypeIter = 
                        typedefs.types.find(TypeDefinitionsImpl::getTypeQName(prop.typeUri, prop.typeName));
                    if(propTypeIter != typedefs.types.end())
                    {
                        prop.typeName = propTypeIter->second.name;
                    }

                    dataFactory->addPropertyToType(
                        (const char*)ty.uri, 
                        (const char*)ty.name,
                        (const char*)prop.name,
                        (const char*)prop.typeUri,
                        (const char*)prop.typeName,
                        prop.isMany,
                        prop.isReadOnly,
                        prop.isContainment);

                    LOGINFO_1(INFO,"XSDHelper adds property %s",(const char*)(prop.name));

                    if (!prop.aliases.isNull())
                    {
                      SDOXMLString al = prop.aliases;
                      SDOXMLString al1 = "";
                      int index;
                      while (!al.isNull() && !al.equals(""))
                      {
                        index = al.lastIndexOf(' ');
                        if (index < 0)
                        {
                          al1 = al;
                          al = "";
                        }
                        else
                        {
                          al1 = al.substring(index + 1);
                          al = al.substring(0,index);
                        }
                        if (!al1.isNull() && !al1.equals(""))
                        {
                          dataFactory->setAlias(
                              (const char*)ty.uri,
                              (const char*)ty.name,
                              (const char*)prop.name,
                              (const char*)al1);
                        }
                      }
                    }
                    if (prop.isSubstitute)
                    {
                      // we have a new substitute - so will check the existing graph for
                      // substitutions to insert
                            
                      LOGINFO_1(INFO,"XSDHelper: Found new substitute %s",(const char*)(prop.name));
                      
                      newSubstitute( "RootType",prop);
                    }
                    else
                    {
                      // subtitutes can only reference global types
                      if (ty.name.equals("RootType"))
                      {
                        addSubstitutes(prop,ty);
                      }
                    }
                        
                    // Do not add DASValue to ChangeSummary
                    if (!(prop.typeUri.equals(SDOUtils::sdoURI)
                          && prop.typeName.equals("ChangeSummary")))
                    {
                      dataFactory->setDASValue(
                          ty.uri, 
                          ty.name,
                          prop.name,
                          "XMLDAS::PropertyInfo",
                          new XSDPropertyInfo(prop));
                    }
                } // for
            } // for

            // The addition of the new types was succesful so adding these
            // types to the defined types should succeed (no duplicates)
            definedTypes.getTypeDefinitions().addTypeDefinitions(typedefs);

        } // End - defineTypes
        
        /**  getDataFactory returns the factory
         *
         */

        DataFactoryPtr XSDHelperImpl::getDataFactory()
        {
            return dataFactory;
        }
        
        
        /**  generate buildsXSD from types/properties
         *
         * generate/generateFile
         *
         * Saves the types/properties to an XSD stream or file
         *
         */

        void XSDHelperImpl::generateFile(
            const TypeList& types,
            const char* fileName, 
            const char* targetNamespaceURI,
            int indent
            )
        {
            SDOXSDFileWriter writer(fileName);
            writer.write(types, targetNamespaceURI, 
                dataFactory->getOpenProperties(), indent);
        }
        void XSDHelperImpl::generateFile(
            const TypeList& types,
            const SDOString& fileName, 
            const SDOString& targetNamespaceURI,
            int indent
            )
        {
            SDOXSDFileWriter writer(fileName.c_str());
            writer.write(types, targetNamespaceURI.c_str(), 
                dataFactory->getOpenProperties(), indent);
        }
        
        void XSDHelperImpl::generate(
            const TypeList& types,
            std::ostream& outXml,
            const char* targetNamespaceURI,
            int indent
            )
        {
            SDOXSDStreamWriter writer(outXml);
            writer.write(types,    targetNamespaceURI, 
                dataFactory->getOpenProperties(), indent);                
        }
        void XSDHelperImpl::generate(
            const TypeList& types,
            std::ostream& outXml,
            const SDOString& targetNamespaceURI,
            int indent
            )
        {
            SDOXSDStreamWriter writer(outXml);
            writer.write(types,    targetNamespaceURI.c_str(), 
                dataFactory->getOpenProperties(), indent);                
        }
        
        char* XSDHelperImpl::generate(
            const TypeList& types,
            const char* targetNamespaceURI,
            int indent
            )
        {
            SDOXSDBufferWriter writer;
            writer.write(types, targetNamespaceURI, 
                dataFactory->getOpenProperties(), indent);
            SDOXMLString ret = writer.getBuffer();
            char* retString = new char[strlen(ret) +1];
            strcpy(retString, ret);
            return retString;
        }
        char* XSDHelperImpl::generate(
            const TypeList& types,
            const SDOString& targetNamespaceURI,
            int indent
            )
        {
            SDOXSDBufferWriter writer;
            writer.write(types, targetNamespaceURI.c_str(), 
                dataFactory->getOpenProperties(), indent);
            SDOXMLString ret = writer.getBuffer();
            char* retString = new char[strlen(ret) +1];
            strcpy(retString, ret);
            return retString;
        }

        unsigned int XSDHelperImpl::getErrorCount() const
        {
            return parseErrors.size();
        }


        const char* XSDHelperImpl::getErrorMessage(unsigned int errnum) const
        {
            if (errnum >= 0 && errnum < parseErrors.size())
            {
                return parseErrors[errnum];
            }
            return 0;
        }

        void XSDHelperImpl::setError(const char* message)
        {
            if (message == 0) return;
            char * m = new char[strlen(message) + 1];
            strcpy(m,message);
            m[strlen(message)] = 0;
            parseErrors.push_back(m);
        }

        void XSDHelperImpl::clearErrors()
        {
            while (!parseErrors.empty()) 
            {
                if (*parseErrors.begin() != 0)
                {
                    delete[] (char*)(*parseErrors.begin());
                }
                parseErrors.erase(parseErrors.begin());
            }
        }
        
        
    } // End - namespace sdo
} // End - namespace commonj
