/******************************************************************************
 *
 * Project:  ISO8211 library
 * Purpose:  Create a 8211 file from a XML dump file generated by "8211dump -xml"
 * Author:   Even Rouault <even dot rouault at spatialys.com>
 *
 ******************************************************************************
 * Copyright (c) 2013, Even Rouault <even dot rouault at spatialys.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "cpl_conv.h"
#include "cpl_minixml.h"
#include "cpl_string.h"
#include "iso8211.h"
#include <map>
#include <string>

CPL_CVSID("$Id$")

int main(int nArgc, char* papszArgv[])
{
    const char *pszFilename = nullptr;
    const char *pszOutFilename = nullptr;

/* -------------------------------------------------------------------- */
/*      Check arguments.                                                */
/* -------------------------------------------------------------------- */
    for( int iArg = 1; iArg < nArgc; iArg++ )
    {
        if( pszFilename == nullptr )
        {
            pszFilename = papszArgv[iArg];
        }
        else if( pszOutFilename == nullptr )
        {
            pszOutFilename = papszArgv[iArg];
        }
        else
        {
            pszFilename = nullptr;
            break;
        }
    }

    if( pszFilename == nullptr )
    {
        printf( "Usage: 8211createfromxml filename.xml outfilename\n" );
        exit( 1 );
    }

    CPLXMLNode* poRoot = CPLParseXMLFile( pszFilename );
    if( poRoot == nullptr )
    {
        fprintf(stderr, "Cannot parse XML file '%s'\n", pszFilename);
        exit( 1 );
    }

    CPLXMLNode* poXMLDDFModule = CPLSearchXMLNode(poRoot, "=DDFModule");
    if( poXMLDDFModule == nullptr )
    {
        fprintf(stderr, "Cannot find DDFModule node in XML file '%s'\n",
                pszFilename);
        exit( 1 );
    }

    /* Compute the size of the DDFField tag */
    CPLXMLNode* psIter = poXMLDDFModule->psChild;
    int nSizeFieldTag = 0;
    while( psIter != nullptr )
    {
        if( psIter->eType == CXT_Element &&
            strcmp(psIter->pszValue, "DDFFieldDefn") == 0 )
        {
            const char* pszTag = CPLGetXMLValue(psIter, "tag", "");
            if( nSizeFieldTag == 0 )
                nSizeFieldTag = (int)strlen(pszTag);
            else if( nSizeFieldTag != (int)strlen(pszTag) )
            {
                fprintf(stderr, "All fields have not the same tag size\n");
                exit( 1 );
            }
        }
        psIter = psIter->psNext;
    }

    char chInterchangeLevel = '3';
    chInterchangeLevel =
        CPLGetXMLValue(poXMLDDFModule, "_interchangeLevel",
                       CPLSPrintf("%c", chInterchangeLevel))[0];

    char chLeaderIden = 'L';
    chLeaderIden =
        CPLGetXMLValue(poXMLDDFModule, "_leaderIden",
                       CPLSPrintf("%c", chLeaderIden))[0];

    char chCodeExtensionIndicator = 'E';
    chCodeExtensionIndicator =
        CPLGetXMLValue(poXMLDDFModule, "_inlineCodeExtensionIndicator",
                       CPLSPrintf("%c", chCodeExtensionIndicator))[0];

    char chVersionNumber = '1';
    chVersionNumber = CPLGetXMLValue(poXMLDDFModule, "_versionNumber",
                                     CPLSPrintf("%c", chVersionNumber))[0];

    char chAppIndicator = ' ';
    chAppIndicator = CPLGetXMLValue(poXMLDDFModule, "_appIndicator",
                                    CPLSPrintf("%c", chAppIndicator))[0];

    const char *pszExtendedCharSet = " ! ";
    char szExtendedCharSet[4];
    snprintf(szExtendedCharSet, sizeof(szExtendedCharSet), "%s",
             CPLGetXMLValue(poXMLDDFModule, "_extendedCharSet",
                            pszExtendedCharSet));
    pszExtendedCharSet = szExtendedCharSet;
    int nSizeFieldLength = 3;
    nSizeFieldLength = atoi(CPLGetXMLValue(poXMLDDFModule, "_sizeFieldLength",
                                           CPLSPrintf("%d", nSizeFieldLength)));

    int nSizeFieldPos = 4;
    nSizeFieldPos = atoi(CPLGetXMLValue(poXMLDDFModule, "_sizeFieldPos",
                                        CPLSPrintf("%d", nSizeFieldPos)));
    nSizeFieldTag = atoi(CPLGetXMLValue(poXMLDDFModule, "_sizeFieldTag",
                                        CPLSPrintf("%d", nSizeFieldTag)));

    DDFModule oModule;
    oModule.Initialize(chInterchangeLevel,
                       chLeaderIden,
                       chCodeExtensionIndicator,
                       chVersionNumber,
                       chAppIndicator,
                       pszExtendedCharSet,
                       nSizeFieldLength,
                       nSizeFieldPos,
                       nSizeFieldTag);
    oModule.SetFieldControlLength(atoi(
        CPLGetXMLValue(poXMLDDFModule, "_fieldControlLength",
                       CPLSPrintf("%d", oModule.GetFieldControlLength()))));

    bool bCreated = false;

    // Create DDFFieldDefn and DDFRecord elements.
    psIter = poXMLDDFModule->psChild;
    while( psIter != nullptr )
    {
        if( psIter->eType == CXT_Element &&
            strcmp(psIter->pszValue, "DDFFieldDefn") == 0 )
        {
            DDFFieldDefn* poFDefn = new DDFFieldDefn();

            DDF_data_struct_code eStructCode = dsc_elementary;
            const char* pszStructCode =
                CPLGetXMLValue(psIter, "dataStructCode", "");
            if( strcmp(pszStructCode, "elementary") == 0 )
                eStructCode = dsc_elementary;
            else if( strcmp(pszStructCode, "vector") == 0 )
                eStructCode = dsc_vector;
            else if( strcmp(pszStructCode, "array") == 0 )
                eStructCode = dsc_array;
            else if( strcmp(pszStructCode, "concatenated") == 0 )
                eStructCode = dsc_concatenated;

            DDF_data_type_code eTypeCode = dtc_char_string;
            const char* pszTypeCode =
                CPLGetXMLValue(psIter, "dataTypeCode", "");
            if( strcmp(pszTypeCode, "char_string") == 0 )
                eTypeCode = dtc_char_string;
            else if( strcmp(pszTypeCode, "implicit_point") == 0 )
                eTypeCode = dtc_implicit_point;
            else if( strcmp(pszTypeCode, "explicit_point") == 0 )
                eTypeCode = dtc_explicit_point;
            else if( strcmp(pszTypeCode, "explicit_point_scaled") == 0 )
                eTypeCode = dtc_explicit_point_scaled;
            else if( strcmp(pszTypeCode, "char_bit_string") == 0 )
                eTypeCode = dtc_char_bit_string;
            else if( strcmp(pszTypeCode, "bit_string") == 0 )
                eTypeCode = dtc_bit_string;
            else if( strcmp(pszTypeCode, "mixed_data_type") == 0 )
                eTypeCode = dtc_mixed_data_type;

            const char* pszFormatControls =
                CPLGetXMLValue(psIter, "formatControls", nullptr);
            if( eStructCode != dsc_elementary )
                pszFormatControls = nullptr;

            const char* pszArrayDescr =
                CPLGetXMLValue(psIter, "arrayDescr", "");
            if( eStructCode == dsc_vector )
                pszArrayDescr = "";
            else if( eStructCode == dsc_array )
                pszArrayDescr = "*";

            poFDefn->Create( CPLGetXMLValue(psIter, "tag", ""),
                             CPLGetXMLValue(psIter, "fieldName", ""),
                             pszArrayDescr,
                             eStructCode, eTypeCode,
                             pszFormatControls );

            CPLXMLNode* psSubIter = psIter->psChild;
            while( psSubIter != nullptr )
            {
                if( psSubIter->eType == CXT_Element &&
                    strcmp(psSubIter->pszValue, "DDFSubfieldDefn") == 0 )
                {
                    poFDefn->AddSubfield(
                        CPLGetXMLValue(psSubIter, "name", ""),
                        CPLGetXMLValue(psSubIter, "format", "") );
                }
                psSubIter = psSubIter->psNext;
            }

            pszFormatControls = CPLGetXMLValue(psIter, "formatControls", nullptr);
            if( pszFormatControls )
                poFDefn->SetFormatControls(pszFormatControls);

            oModule.AddField( poFDefn );
        }
        else if( psIter->eType == CXT_Element &&
                 strcmp(psIter->pszValue, "DDFRecord") == 0 )
        {
            // const bool bFirstRecord = !bCreated;
            if( !bCreated )
            {
                oModule.Create( pszOutFilename );
                bCreated = true;
            }

            DDFRecord *poRec = new DDFRecord( &oModule );
            std::map<std::string, int> oMapField;

            // if( !bFirstRecord )
            //     poRec->SetReuseHeader(atoi(
            //         CPLGetXMLValue(psIter, "reuseHeader",
            //         CPLSPrintf("%d", poRec->GetReuseHeader()))));
            poRec->SetSizeFieldLength(atoi(
                CPLGetXMLValue(psIter, "_sizeFieldLength",
                               CPLSPrintf("%d", poRec->GetSizeFieldLength()))));
            poRec->SetSizeFieldPos(atoi(
                CPLGetXMLValue(psIter, "_sizeFieldPos",
                               CPLSPrintf("%d", poRec->GetSizeFieldPos()))));
            poRec->SetSizeFieldTag(atoi(
                CPLGetXMLValue(psIter, "_sizeFieldTag",
                               CPLSPrintf("%d", poRec->GetSizeFieldTag()))));

            CPLXMLNode* psSubIter = psIter->psChild;
            while( psSubIter != nullptr )
            {
                if( psSubIter->eType == CXT_Element &&
                    strcmp(psSubIter->pszValue, "DDFField") == 0 )
                {
                    const char* pszFieldName =
                        CPLGetXMLValue(psSubIter, "name", "");
                    DDFFieldDefn* poFieldDefn =
                        oModule.FindFieldDefn( pszFieldName );
                    if( poFieldDefn == nullptr )
                    {
                        fprintf(stderr, "Can't find field '%s'\n",
                                pszFieldName );
                        exit(1);
                    }

                    int nFieldOcc = oMapField[pszFieldName];
                    oMapField[pszFieldName] ++ ;

                    DDFField *poField = poRec->AddField( poFieldDefn );
                    const char* pszValue =
                        CPLGetXMLValue(psSubIter, "value", nullptr);
                    if( pszValue != nullptr && STARTS_WITH(pszValue, "0x") )
                    {
                        pszValue += 2;
                        int nDataLen = (int)strlen(pszValue) / 2;
                        char* pabyData = (char*) malloc(nDataLen);
                        for(int i=0;i<nDataLen;i++)
                        {
                            char c;
                            int nHigh, nLow;
                            c = pszValue[2*i];
                            if( c >= 'A' && c <= 'F' )
                                nHigh = 10 + c - 'A';
                            else
                                nHigh = c - '0';
                            c = pszValue[2*i + 1];
                            if( c >= 'A' && c <= 'F' )
                                nLow = 10 + c - 'A';
                            else
                                nLow = c - '0';
                            pabyData[i] = (nHigh << 4) + nLow;
                        }
                        poRec->SetFieldRaw( poField, nFieldOcc,
                                            (const char *) pabyData, nDataLen );
                        free(pabyData);
                    }
                    else
                    {
                        CPLXMLNode* psSubfieldIter = psSubIter->psChild;
                        std::map<std::string, int> oMapSubfield;
                        while( psSubfieldIter != nullptr )
                        {
                            if( psSubfieldIter->eType == CXT_Element &&
                                strcmp(psSubfieldIter->pszValue,
                                       "DDFSubfield") == 0 )
                            {
                                const char* pszSubfieldName =
                                    CPLGetXMLValue(psSubfieldIter, "name", "");
                                const char* pszSubfieldType =
                                    CPLGetXMLValue(psSubfieldIter, "type", "");
                                const char* pszSubfieldValue =
                                    CPLGetXMLValue(psSubfieldIter, nullptr, "");
                                int nOcc = oMapSubfield[pszSubfieldName];
                                oMapSubfield[pszSubfieldName] ++ ;
                                if( strcmp(pszSubfieldType, "float") == 0 )
                                {
                                    poRec->SetFloatSubfield(
                                        pszFieldName, nFieldOcc,
                                        pszSubfieldName, nOcc,
                                        CPLAtof(pszSubfieldValue) );
                                }
                                else if( strcmp(pszSubfieldType,
                                                "integer") == 0 )
                                {
                                    poRec->SetIntSubfield(
                                        pszFieldName, nFieldOcc,
                                        pszSubfieldName, nOcc,
                                        atoi(pszSubfieldValue) );
                                }
                                else if( strcmp(pszSubfieldType,
                                                "string") == 0 )
                                {
                                    poRec->SetStringSubfield(
                                        pszFieldName, nFieldOcc,
                                        pszSubfieldName, nOcc,
                                        pszSubfieldValue );
                                }
                                else if( strcmp(pszSubfieldType,
                                                "binary") == 0 &&
                                         STARTS_WITH(pszSubfieldValue, "0x") )
                                {
                                    pszSubfieldValue += 2;
                                    int nDataLen =
                                        (int)strlen(pszSubfieldValue) / 2;
                                    char* pabyData = (char*) malloc(nDataLen);
                                    for(int i=0;i<nDataLen;i++)
                                    {
                                        int nHigh;
                                        int nLow;
                                        char c = pszSubfieldValue[2*i];
                                        if( c >= 'A' && c <= 'F' )
                                            nHigh = 10 + c - 'A';
                                        else
                                            nHigh = c - '0';
                                        c = pszSubfieldValue[2*i + 1];
                                        if( c >= 'A' && c <= 'F' )
                                            nLow = 10 + c - 'A';
                                        else
                                            nLow = c - '0';
                                        pabyData[i] = (nHigh << 4) + nLow;
                                    }
                                    poRec->SetStringSubfield(
                                        pszFieldName, nFieldOcc,
                                        pszSubfieldName, nOcc,
                                        pabyData, nDataLen );
                                    free(pabyData);
                                }
                            }
                            psSubfieldIter = psSubfieldIter->psNext;
                        }
                    }
                }
                psSubIter = psSubIter->psNext;
            }

            poRec->Write();
            delete poRec;
        }

        psIter = psIter->psNext;
    }

    CPLDestroyXMLNode(poRoot);

    oModule.Close();

    return 0;
}
