/*
 * Copyright (C) 2004-2006  Autodesk, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of version 2.1 of the GNU Lesser
 * General Public License as published by the Free Software Foundation.
 * 
 * This library 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 library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "stdafx.h"
#include "FdoWfsSchemaMerger.h"
#include "FdoWfsSchemaRefHandler.h"
#include <OWS/FdoOwsIHttpHandler.h>
#include <FdoCommonStringUtil.h>
#include "FdoWfsSchemaCopyHandler.h"
#include "FdoWfsNamedIoStream.h"
#include "feature.h"
#include "geometry.h"
#include "xlinks.h"
#include "Gml311/feature3.h"
#include "Gml311/basicTypes3.h"
#include "Gml311/coordinateOperations3.h"
#include "Gml311/coordinateReferenceSystems3.h"
#include "Gml311/coordinateSystems3.h"
#include "Gml311/dataQuality3.h"
#include "Gml311/dictionary3.h"
#include "Gml311/direction3.h"
#include "Gml311/geometryAggregates3.h"
#include "Gml311/geometryBasic0d1d3.h"
#include "Gml311/geometryBasic2d3.h"
#include "Gml311/geometryComplexes3.h"
#include "Gml311/geometryPrimitives3.h"
#include "Gml311/gml3.h"
#include "Gml311/gmlBase3.h"
#include "Gml311/grids3.h"
#include "Gml311/measures3.h"
#include "Gml311/referenceSystems3.h"
#include "Gml311/temporal3.h"
#include "Gml311/temporalReferenceSystems3.h"
#include "Gml311/units3.h"
#include "Gml311/valueObjects3.h"

#include "Gml321/feature321.h"
#include "Gml321/basicTypes321.h"
#include "Gml321/coordinateOperations321.h"
#include "Gml321/coordinateReferenceSystems321.h"
#include "Gml321/coordinateSystems321.h"
#include "Gml321/coverage321.h"
#include "Gml321/datums321.h"
#include "Gml321/deprecatedTypes321.h"
#include "Gml321/dictionary321.h"
#include "Gml321/direction321.h"
#include "Gml321/dynamicFeature321.h"
#include "Gml321/geometryAggregates321.h"
#include "Gml321/geometryBasic0d1d321.h"
#include "Gml321/geometryBasic2d321.h"
#include "Gml321/geometryComplexes321.h"
#include "Gml321/geometryPrimitives321.h"
#include "Gml321/gml321.h"
#include "Gml321/gmlBase321.h"
#include "Gml321/grids321.h"
#include "Gml321/measures321.h"
#include "Gml321/observation321.h"
#include "Gml321/referenceSystems321.h"
#include "Gml321/temporal321.h"
#include "Gml321/temporalReferenceSystems321.h"
#include "Gml321/temporalTopology321.h"
#include "Gml321/topology321.h"
#include "Gml321/units321.h"
#include "Gml321/valueObjects321.h"

FdoWfsSchemaMerger::FdoWfsSchemaMerger()
{
    m_FeatureLoaded = false;
    m_GeometryLoaded = false;
    m_XlinksLoaded = false;
    m_mergedList = NULL;
    m_loadedStreams = NULL;
    m_copier = NULL;
}

FdoWfsSchemaMerger::FdoWfsSchemaMerger(FdoString* version)
{
    m_FeatureLoaded = false;
    m_GeometryLoaded = false;
    m_XlinksLoaded = false;
    m_mergedList = NULL;
    m_loadedStreams = NULL;
    m_copier = NULL;
	m_version = version;
	
	// add the GML3.1.1 basic schemas
	if (wcscmp(m_version,FdoWfsGlobals::WfsVersion110) == 0)
	{
		m_Gml311[L"feature.xsd"] = (char**)feature3;
		m_Gml311[L"basicTypes.xsd"] = (char**)basicTypes3;
		m_Gml311[L"coordinateOperations.xsd"] = (char**)coordinateOperations3;
		m_Gml311[L"coordinateReferenceSystems.xsd"] = (char**)coordinateReferenceSystems3;
		m_Gml311[L"coordinateSystems.xsd"] = (char**)coordinateSystems3;
		m_Gml311[L"dataQuality.xsd"] = (char**)dataQuality3;
		m_Gml311[L"dictionary.xsd"] = (char**)dictionary3;
		m_Gml311[L"direction.xsd"] = (char**)direction3;
		m_Gml311[L"geometryAggregates.xsd"] = (char**)geometryAggregates3;
		m_Gml311[L"geometryBasic0d1d.xsd"] = (char**)geometryBasic0d1d3;
		m_Gml311[L"geometryBasic2d.xsd"] = (char**)geometryBasic2d3;
		m_Gml311[L"geometryComplexes.xsd"] = (char**)geometryComplexes3;
		m_Gml311[L"geometryPrimitives.xsd"] = (char**)geometryPrimitives3;
		m_Gml311[L"gml.xsd"] = (char**)gml3;
		m_Gml311[L"gmlBase.xsd"] = (char**)gmlBase3;
		m_Gml311[L"grids.xsd"] = (char**)grids3;
		m_Gml311[L"measures.xsd"] = (char**)measures3;
		m_Gml311[L"referenceSystems.xsd"] = (char**)referenceSystems3;
		m_Gml311[L"temporal.xsd"] = (char**)temporal3;
		m_Gml311[L"temporalReferenceSystems.xsd"] = (char**)temporalReferenceSystems3;
		m_Gml311[L"units.xsd"] = (char**)units3;
		m_Gml311[L"valueObjects.xsd"] = (char**)valueObjects3;
	}
	else if (wcscmp(m_version, FdoWfsGlobals::WfsVersion200) == 0) // add the GML3.2.1 basic schemas
	{
		m_Gml321[L"feature.xsd"] = (char**)feature321;
		m_Gml321[L"basicTypes.xsd"] = (char**)basicTypes321;
		m_Gml321[L"coordinateOperations.xsd"] = (char**)coordinateOperations321;
		m_Gml321[L"coordinateReferenceSystems.xsd"] = (char**)coordinateReferenceSystems321;
		m_Gml321[L"coordinateSystems.xsd"] = (char**)coordinateSystems321;
		m_Gml321[L"coverage.xsd"] = (char**)coverage321;
		m_Gml321[L"datums.xsd"] = (char**)datums321;
		m_Gml321[L"deprecatedTypes.xsd"] = (char**)deprecatedTypes321;
		m_Gml321[L"dictionary.xsd"] = (char**)dictionary321;
		m_Gml321[L"direction.xsd"] = (char**)direction321;
		m_Gml321[L"dynamicFeature.xsd"] = (char**)dynamicFeature321;
		m_Gml321[L"geometryAggregates.xsd"] = (char**)geometryAggregates321;
		m_Gml321[L"geometryBasic0d1d.xsd"] = (char**)geometryBasic0d1d321;
		m_Gml321[L"geometryBasic2d.xsd"] = (char**)geometryBasic2d321;
		m_Gml321[L"geometryComplexes.xsd"] = (char**)geometryComplexes321;
		m_Gml321[L"geometryPrimitives.xsd"] = (char**)geometryPrimitives321;
		m_Gml321[L"gml.xsd"] = (char**)gml321;
		m_Gml321[L"gmlBase.xsd"] = (char**)gmlBase321;
		m_Gml321[L"grids.xsd"] = (char**)grids321;
		m_Gml321[L"measures.xsd"] = (char**)measures321;
		m_Gml321[L"observation.xsd"] = (char**)observation321;
		m_Gml321[L"referenceSystems.xsd"] = (char**)referenceSystems321;
		m_Gml321[L"temporal.xsd"] = (char**)temporal321;
		m_Gml321[L"temporalReferenceSystems.xsd"] = (char**)temporalReferenceSystems321;
		m_Gml321[L"temporalTopology.xsd"] = (char**)temporalTopology321;
		m_Gml321[L"topology.xsd"] = (char**)topology321;
		m_Gml321[L"units.xsd"] = (char**)units321;
		m_Gml321[L"valueObjects.xsd"] = (char**)valueObjects321;
	}
}


FdoWfsSchemaMerger::~FdoWfsSchemaMerger()
{
	if (!m_Gml311.empty())
		m_Gml311.clear();
	if (!m_Gml321.empty())
		m_Gml321.clear();
}

FdoIoStream* FdoWfsSchemaMerger::MergeSchema(FdoIoStream* schema, FdoString* schemaLocation, FdoString* uri)
{
    m_FeatureLoaded = false;
    m_GeometryLoaded = false;
    m_XlinksLoaded = false;
    m_mergedList = FdoDictionary::Create();
    m_loadedStreams = FdoWfsNamedIoStreamCollection::Create();
    m_mergedSchema = FdoIoMemoryStream::Create();
    FdoXmlWriterP xmlWriter = FdoXmlWriter::Create( m_mergedSchema );
    m_copier = FdoWfsSchemaCopyHandler::Create(xmlWriter);
    xmlWriter = NULL;


    _mergeSchema(schema, schemaLocation, uri);

    m_mergedList = NULL;
    m_loadedStreams = NULL;
    m_copier = NULL;
    m_mergedSchema->Reset();

    return FDO_SAFE_ADDREF(m_mergedSchema.p);
}


void FdoWfsSchemaMerger::_mergeSchema(FdoIoStream* schema, FdoString* schemaLocation, FdoString* uri)
{
    // first we must check whether it is already merged
    // similar as "#ifndef _XXX_H"
    FdoPtr<FdoDictionaryElement> item = m_mergedList->FindItem(schemaLocation);
    if (item != NULL)
        return;

    // if not merged, mark itself as merged
    // similar as "#define _XXX_H"
    item = FdoDictionaryElement::Create( schemaLocation, uri );
    m_mergedList->Add(item);
    
    // get all refs
    FdoXmlReaderP srcReader = FdoXmlReader::Create(schema);
    
    FdoPtr<FdoWfsSchemaRefHandler> refHandler = FdoWfsSchemaRefHandler::Create();

    srcReader->Parse(refHandler.p);
    FdoPtr<FdoDictionary> refs = refHandler->GetRefs();
    FdoPtr<FdoStringCollection> refFullLocations = FdoStringCollection::Create();
    FdoPtr<FdoStringCollection> refUris = FdoStringCollection::Create();

    // try to retrieve all the refs
    FdoInt32 count = refs->GetCount();
    int i;
    for (i = 0; i < count; i++)
    {
        FdoPtr<FdoDictionaryElement> ref = refs->GetItem(i);
        FdoString* location = ref->GetName();
        FdoStringP fullLocation = _getFullLocation(location, schemaLocation);
        // check whether it is merged
        FdoPtr<FdoDictionaryElement> item = m_mergedList->FindItem((FdoString*)fullLocation);
        if (item == NULL)
        {
            // check whether it is already loaded
            FdoPtr<FdoWfsNamedIoStream> loadedStream = m_loadedStreams->FindItem((FdoString*)fullLocation);
            if (loadedStream == NULL)
            {
                FdoPtr<FdoIoStream> stream;
                char** xmlSchema = NULL;
				// if it is a well known GML schema, then do not get it from outside
				if (wcscmp(m_version,FdoWfsGlobals::WfsVersion) == 0) //1.0.0 version, use GML 2.1.2
				{
					if (fullLocation.Contains(L"feature.xsd"))
					{
						if (m_FeatureLoaded)
							continue;
						m_FeatureLoaded = true;
						xmlSchema = (char**)feature;
					}
					else if (fullLocation.Contains(L"geometry.xsd"))
					{
						if (m_GeometryLoaded)
							continue;
						m_GeometryLoaded = true;
						xmlSchema = (char**)geometry;
					}
				}
				else if (wcscmp(m_version,FdoWfsGlobals::WfsVersion110) == 0) //1.1.0 version, use GML 3.1.1
				{
					// don't need to merge the unneeded parts
					if (fullLocation.Contains(L"dynamicFeature.xsd")|| fullLocation.Contains(L"defaultStyle.xsd") || fullLocation.Contains(L"datums.xsd") ||
						fullLocation.Contains(L"smil20-language.xsd") ||  fullLocation.Contains(L"xml-mod.xsd") || fullLocation.Contains(L"smil20.xsd") ||
						fullLocation.Contains(L"temporalTopology.xsd") || fullLocation.Contains(L"topology.xsd") ||
						fullLocation.Contains(L"coverage.xsd") || fullLocation.Contains(L"observation.xsd") )
						continue;
					else // found in the m_Gml313 map
					{
						//get the xsd name
						FdoStringP name = this->_getXSDName(fullLocation);
						std::map<FdoStringP,char** >::iterator iter = m_Gml311.find(name);
						if(iter != m_Gml311.end())
							xmlSchema = iter->second;
					}
				}
				else if (wcscmp(m_version, FdoWfsGlobals::WfsVersion200) == 0) //2.0.0 version, use GML 3.2.1
				{
					// don't need to merge the unneeded parts
					if (fullLocation.Contains(L"dynamicFeature.xsd") || fullLocation.Contains(L"coverage.xsd") ||
						fullLocation.Contains(L"xml.xsd") || fullLocation.Contains(L"xlink.xsd") || 
						fullLocation.Contains(L"gmd.xsd"))
						continue;
					else // found in the m_Gml321 map
					{
						//get the xsd name
						FdoStringP name = this->_getXSDName(fullLocation);
						std::map<FdoStringP, char** >::iterator iter = m_Gml321.find(name);
						if (iter != m_Gml321.end())
							xmlSchema = iter->second;
					}
				}
                
				if (fullLocation.Contains(L"xlinks.xsd")) //xlinks schema is same between GML 2 and GML 3
                {
                    if (m_XlinksLoaded)
                        continue;
                    m_XlinksLoaded = true;
                    xmlSchema = (char**)xlinks;
                }
                refFullLocations->Add(fullLocation);
                refUris->Add(ref->GetValue());
                if (xmlSchema != NULL)
                {
                    stream = FdoIoMemoryStream::Create();
                    int i = 0;
                    while (xmlSchema[i] != NULL)
                    {
                        stream->Write((FdoByte*)xmlSchema[i], strlen(xmlSchema[i]));
                        i++;
                    }
                    stream->Reset();
                }
                else
                {
                    char* mbUrl;
                    wide_to_multibyte(mbUrl, (FdoString*)fullLocation);
                    FdoPtr<FdoOwsIHttpHandler> httpHandler = FdoOwsIHttpHandler::Create(mbUrl, true, "", "", "");
                    httpHandler->Perform();
                    stream = static_cast<FdoIoStream*>(FDO_SAFE_ADDREF(httpHandler.p));
                }
                FdoPtr<FdoWfsNamedIoStream> item = FdoWfsNamedIoStream::Create(fullLocation, stream);
                m_loadedStreams->Add(item);
            }
        }
    }
    count = refFullLocations->GetCount();
    // recursively merge all the unmerged refs first
    for (i = 0; i < count; i++)
    {
        FdoString* fullLocation = refFullLocations->GetString(i);
        FdoPtr<FdoWfsNamedIoStream> namedStream = m_loadedStreams->GetItem(fullLocation);
        FdoPtr<FdoIoStream> stream = namedStream->GetStream();
        FdoString* uri = refUris->GetString(i);
        // call recursively
        _mergeSchema(stream, fullLocation, uri);
    }

    // finally merge itself
    schema->Reset();
    srcReader = FdoXmlReader::Create(schema);
    srcReader->Parse(m_copier);

}

FdoStringP FdoWfsSchemaMerger::_getXSDName(FdoStringP location)
{
	FdoStringP rv;

	size_t len = location.GetLength();
	size_t i;
    size_t pos = len -1;
	for (i = len - 1;i>=0;i--)
	{
         if (*((FdoString*)location + i ) == FdoWfsGlobals::QUESTION)
             pos = i - 1;
         else if (*((FdoString*)location + i) == FdoWfsGlobals::SLASH)
              break;
	}

	if ( i == -1)
		rv = L"";
	else
		rv = location.Mid(i + 1, pos - i);
	return rv;

}

FdoStringP FdoWfsSchemaMerger::_getFullLocation(FdoString* relativeLocation, FdoString* baseLocation)
{
    FdoStringP rv = relativeLocation;
    if (!rv.Contains(FdoWfsGlobals::HTTPProtocal))
    {
        rv = baseLocation;
        // find the last "/"
        size_t len = rv.GetLength();
        size_t i;
        for (i = len - 1; i >= 0; i--)
        {
            if (*((FdoString*)rv + i) == FdoWfsGlobals::SLASH)
                break;
        }
        if (i == -1) // not found
            rv = relativeLocation;
        else
            rv = rv.Mid(0, i + 1) + relativeLocation;
    }

    // Strip out any ./ parts in the full URI. These don't make any difference in an absolute path.
    // They also get tacked on indefinitely when processing recursive schema includes, causing infinite
    // recursion. The Schema Merger does not recognize when a schema is processed twice, since the extra ./
    // makes it look like a different schema.
    while (rv.Contains(L"/./"))
        rv = rv.Replace(L"/./",L"/");

    return rv;
}






