/***************************************************************************
 *
 * Project:  OpenCPN
 * Purpose:  S57 Chart Object
 * Author:   David Register
 *
 ***************************************************************************
 *   Copyright (C) 2010 by David S. Register                               *
 *                                                                         *
 *   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 2 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, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,  USA.         *
 **************************************************************************/

// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"

#ifndef  WX_PRECOMP
  #include "wx/wx.h"
#endif //precompiled headers

#include "wx/image.h"                           // for some reason, needed for msvc???
#include "wx/tokenzr.h"
#include <wx/textfile.h>

#include "dychart.h"
#include "OCPNPlatform.h"

#include "s52s57.h"
#include "s52plib.h"

#include "s57chart.h"

#include "mygeom.h"
#include "cutil.h"
#include "georef.h"
#include "navutil.h"                            // for LogMessageOnce
#include "ocpn_pixel.h"
#include "ocpndc.h"
#include "s52utils.h"
#include "wx28compat.h"

#include "gdal/cpl_csv.h"
#include "setjmp.h"

#include "ogr_s57.h"

#include "pluginmanager.h"                      // for S57 lights overlay

#include "Osenc.h"

#ifdef __MSVC__
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__ )
#define new DEBUG_NEW
#endif

#ifdef ocpnUSE_GL
#include "glChartCanvas.h"
#endif

#include <algorithm>          // for std::sort
#include <map>

#ifdef __MSVC__
#define strncasecmp(x,y,z) _strnicmp(x,y,z)
#endif


extern bool              g_b_EnableVBO;

#ifdef ocpnUSE_GL
extern PFNGLDELETEBUFFERSPROC              s_glDeleteBuffers;
#endif

//----------------------------------------------------------------------------------
//      S57Obj CTOR
//----------------------------------------------------------------------------------

S57Obj::S57Obj()
{
    Init();
}

//----------------------------------------------------------------------------------
//      S57Obj DTOR
//----------------------------------------------------------------------------------

S57Obj::~S57Obj()
{
    //  Don't delete any allocated records of simple copy clones
    if( !bIsClone ) {
        if( attVal ) {
            for( unsigned int iv = 0; iv < attVal->GetCount(); iv++ ) {
                S57attVal *vv = attVal->Item( iv );
                void *v2 = vv->value;
                free( v2 );
                delete vv;
            }
            delete attVal;
        }
        free( att_array );

        if( pPolyTessGeo ) {
#ifdef ocpnUSE_GL
            bool b_useVBO = g_b_EnableVBO  && !auxParm1;    // VBO allowed?

            PolyTriGroup *ppg_vbo = pPolyTessGeo->Get_PolyTriGroup_head();
            if (b_useVBO && ppg_vbo && auxParm0 > 0 && ppg_vbo->single_buffer && s_glDeleteBuffers) {
                s_glDeleteBuffers(1, (GLuint *)&auxParm0);
            }
#endif
            delete pPolyTessGeo;
        }

        if( FText ) delete FText;

        if( geoPt ) free( geoPt );
        if( geoPtz ) free( geoPtz );
        if( geoPtMulti ) free( geoPtMulti );

        if( m_lsindex_array ) free( m_lsindex_array );

        if(m_ls_list){
            line_segment_element *element = m_ls_list;
            while(element){
                line_segment_element *next = element->next;
                delete element;
                element = next;
            }
        }
    }
}


void S57Obj::Init()
{
    att_array = NULL;
    attVal = NULL;
    n_attr = 0;

    pPolyTessGeo = NULL;


    bCS_Added = 0;
    CSrules = NULL;
    FText = NULL;
    bFText_Added = 0;
    geoPtMulti = NULL;
    geoPtz = NULL;
    geoPt = NULL;
    bIsClone = false;
    Scamin = 10000000;                              // ten million enough?
    nRef = 0;

    bIsAton = false;
    bIsAssociable = false;
    m_n_lsindex = 0;
    m_lsindex_array = NULL;
    m_n_edge_max_points = 0;
    m_ls_list = 0;
    m_ls_list_legacy = 0;

    iOBJL = -1; // deferred, done by OBJL filtering in the PLIB as needed
    bBBObj_valid = false;

    //        Set default (unity) auxiliary transform coefficients
    x_rate = 1.0;
    y_rate = 1.0;
    x_origin = 0.0;
    y_origin = 0.0;

    auxParm0 = 0;
    auxParm1 = 0;
    auxParm2 = 0;
    auxParm3 = 0;
}

//----------------------------------------------------------------------------------
//      S57Obj CTOR from FeatureName
//----------------------------------------------------------------------------------
S57Obj::S57Obj( const char* featureName )
{
    Init();

    attVal = new wxArrayOfS57attVal();

    strncpy( FeatureName, featureName, 6 );
    FeatureName[6] = 0;

    if( !strncmp( FeatureName, "DEPARE", 6 )
        || !strncmp( FeatureName, "DRGARE", 6 ) ) bIsAssociable = true;

}


bool S57Obj::AddIntegerAttribute( const char *acronym, int val ){

    S57attVal *pattValTmp = new S57attVal;

    int *pAVI = (int *) malloc( sizeof(int) );         //new int;
    *pAVI = val;

    pattValTmp->valType = OGR_INT;
    pattValTmp->value = pAVI;

    att_array = (char *)realloc(att_array, 6*(n_attr + 1));
    strncpy(att_array + (6 * sizeof(char) * n_attr), acronym, 6);
    n_attr++;

    attVal->Add( pattValTmp );

    if(!strncmp(acronym, "SCAMIN", 6))
        Scamin = val;
    
    return true;
}

bool S57Obj::AddIntegerListAttribute( const char *acronym, int *pval, int nValue ){

    return true;
}

bool S57Obj::AddDoubleAttribute( const char *acronym, double val ){

    S57attVal *pattValTmp = new S57attVal;

    double *pAVI = (double *) malloc( sizeof(double) );         //new double;
    *pAVI = val;

    pattValTmp->valType = OGR_REAL;
    pattValTmp->value = pAVI;

    att_array = (char *)realloc(att_array, 6*(n_attr + 1));
    strncpy(att_array + (6 * sizeof(char) * n_attr), acronym, 6);
    n_attr++;

    attVal->Add( pattValTmp );

    return true;
}

bool S57Obj::AddDoubleListAttribute( const char *acronym, double *pval, int nValue ){

    return true;
}

bool S57Obj::AddStringAttribute( const char *acronym, char *val ){

    S57attVal *pattValTmp = new S57attVal;

    char *pAVS = (char *)malloc(strlen(val) + 1);   //new string
    strcpy(pAVS, val);

    pattValTmp->valType = OGR_STR;
    pattValTmp->value = pAVS;

    att_array = (char *)realloc(att_array, 6*(n_attr + 1));
    strncpy(att_array + (6 * sizeof(char) * n_attr), acronym, 6);
    n_attr++;

    attVal->Add( pattValTmp );

    return true;
}

bool S57Obj::SetPointGeometry( double lat, double lon, double ref_lat, double ref_lon)
{
    Primitive_type = GEO_POINT;

    m_lon = lon;
    m_lat = lat;

    //  Set initial BoundingBox limits fairly large...
    BBObj.Set(m_lat - .25, m_lon - .25, m_lat + .25, m_lon + .25);
    bBBObj_valid = false;

    //  Calculate SM from chart common reference point
    double easting, northing;
    toSM( lat, lon, ref_lat, ref_lon, &easting, &northing );

    x = easting;
    y = northing;

    npt = 1;

    return true;
}


bool S57Obj::SetLineGeometry( LineGeometryDescriptor *pGeo, GeoPrim_t geoType, double ref_lat, double ref_lon)
{
    Primitive_type = geoType;

    // set s57obj bbox as lat/lon
    BBObj.Set(pGeo->extent_s_lat, pGeo->extent_w_lon, pGeo->extent_n_lat, pGeo->extent_e_lon);
    bBBObj_valid = true;

    //  and declare x/y of the object to be average east/north of all points
    double e1, e2, n1, n2;
    toSM( pGeo->extent_n_lat, pGeo->extent_e_lon, ref_lat, ref_lon, &e1, &n1 );
    toSM( pGeo->extent_s_lat, pGeo->extent_w_lon, ref_lat, ref_lon, &e2, &n2 );

    x = ( e1 + e2 ) / 2.;
    y = ( n1 + n2 ) / 2.;

    //  Set the object base point
    double xll, yll;
    fromSM( x, y, ref_lat, ref_lon, &yll, &xll );
    m_lon = xll;
    m_lat = yll;

    //  Set the edge and connected node table indices
    m_n_lsindex = pGeo->indexCount;
    m_lsindex_array = pGeo->indexTable;

    m_n_edge_max_points = 0; //TODO this could be precalulated and added to next SENC format

    return true;
}


bool S57Obj::SetAreaGeometry( PolyTessGeo *ppg, double ref_lat, double ref_lon)
{
    Primitive_type = GEO_AREA;
    pPolyTessGeo = ppg;

    //  Set the s57obj bounding box as lat/lon
    BBObj.Set(ppg->Get_ymin(), ppg->Get_xmin(), ppg->Get_ymax(), ppg->Get_xmax());
    bBBObj_valid = true;

    //  and declare x/y of the object to be average east/north of all points
    double e1, e2, n1, n2;
    toSM( ppg->Get_ymax(), ppg->Get_xmax(), ref_lat, ref_lon, &e1,&n1 );
    toSM( ppg->Get_ymin(), ppg->Get_xmin(), ref_lat, ref_lon, &e2,&n2 );

    x = ( e1 + e2 ) / 2.;
    y = ( n1 + n2 ) / 2.;

    //  Set the object base point
    double xll, yll;
    fromSM( x, y, ref_lat, ref_lon, &yll, &xll );
    m_lon = xll;
    m_lat = yll;


    return true;
}

bool S57Obj::SetMultipointGeometry( MultipointGeometryDescriptor *pGeo, double ref_lat, double ref_lon)
{
    Primitive_type = GEO_POINT;

    npt = pGeo->pointCount;

    geoPtz = (double *) malloc( npt * 3 * sizeof(double) );
    geoPtMulti = (double *) malloc( npt * 2 * sizeof(double) );

    double *pdd = geoPtz;
    double *pdl = geoPtMulti;

    float *pfs = (float *) ( pGeo->pointTable);                 // start of point data
    for( int ip = 0; ip < npt; ip++ ) {
        float easting, northing;
        easting = *pfs++;
        northing = *pfs++;
        float depth = *pfs++;

        *pdd++ = easting;
        *pdd++ = northing;
        *pdd++ = depth;

        //  Convert point from SM to lat/lon for later use in decomposed bboxes
        double xll, yll;
        fromSM( easting, northing, ref_lat, ref_lon, &yll, &xll );

        *pdl++ = xll;
        *pdl++ = yll;
    }

    // set s57obj bbox as lat/lon
    BBObj.Set(pGeo->extent_s_lat, pGeo->extent_w_lon, pGeo->extent_n_lat, pGeo->extent_e_lon);
    bBBObj_valid = true;

    return true;
}


int S57Obj::GetAttributeIndex( const char *AttrSeek ) {
    char *patl = att_array;

    for(int i=0 ; i < n_attr ; i++) {
        if(!strncmp(patl, AttrSeek, 6)){
            return i;
            break;
        }

        patl += 6;
    }

    return -1;
}


wxString S57Obj::GetAttrValueAsString( const char *AttrName )
{
    wxString str;

    int idx = GetAttributeIndex(AttrName);

    if(idx >= 0) {

//      using idx to get the attribute value

        S57attVal *v = attVal->Item( idx );

        switch( v->valType ){
            case OGR_STR: {
                char *val = (char *) ( v->value );
                str.Append( wxString( val, wxConvUTF8 ) );
                break;
            }
            case OGR_REAL: {
                double dval = *(double*) ( v->value );
                str.Printf( _T("%g"), dval );
                break;
            }
            case OGR_INT: {
                int ival = *( (int *) v->value );
                str.Printf( _T("%d"), ival );
                break;
            }
            default: {
                str.Printf( _T("Unknown attribute type") );
                break;
            }
        }
    }
    return str;
}
