/* 
* 
* Copyright (C) 2013 UMPC
* 
* This file is part of CLaRiNet.
*  
* CLaRiNet 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 3 of the License, or
* (at your option) any later version.
* 
* CLaRiNet 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 CLaRiNet.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <stddef.h>
#include "GeoMorph.h"
#include "FlowDirGrid.h"
#include "esriheader.h"
#include "Erreur.h"
#include "InOut.h"

#include <cstring>
#include <fstream>
#include <string>
#include <algorithm>
#include <map>

using namespace std;

extern FlowDirGrid ** ptr_FlowDirGrid = NULL;
extern int MaxFlowDir = 0;

int CreateFlowDirGrid (    const char Nom[120],
                          const int ncols,
                          const int nrows,                      
                          const double cellsize,
                          const double xll,
                          const double yll,
                          const float NODATA_value,
                          int LL
                       )
{
	int HandleFlowDir = MaxFlowDir;
	FlowDirGrid **ptr_FlowDirGrid_tmp;
	FlowDirGrid *F;

	/* Initialisation : erreur et valeurs de retour en cas d'erreur */

	ClearErreur();

	/* EcritTrace("GEOMORPH %s - CrÃƒÂ©ation du raster : %s\n", VersionGeoMorph(), Nom); */

	/* Recherche d'un Handle libre ou crÃƒÂ©ation d'un nouveau */

	HandleFlowDir = MaxFlowDir - 1;

	while (HandleFlowDir >= 0)
	{
		if(ptr_FlowDirGrid[HandleFlowDir] == NULL) break;	/* Handle disponible */
		HandleFlowDir--;
	}

	if(HandleFlowDir < 0)							/* Pas de handle disponible */
	{
		if (MaxFlowDir == 0)
		{
			ptr_FlowDirGrid = (FlowDirGrid **) malloc((MaxFlowDir + 1) * sizeof(FlowDirGrid *));
			if (ptr_FlowDirGrid == NULL)
			{
				SetErreur("CreateFlowDirGrid (%s) - could not allocate handle\n", Nom);
				return(-1);
			}
		}
		else
		{
			ptr_FlowDirGrid_tmp = (FlowDirGrid **) realloc(ptr_FlowDirGrid, (MaxFlowDir + 1) * sizeof(FlowDirGrid *));
			if (ptr_FlowDirGrid_tmp == NULL)
			{
				SetErreur("CreateFlowDirGrid (%s) - could not allocate handle\n", Nom);
				return(-1);
			}
			else ptr_FlowDirGrid = ptr_FlowDirGrid_tmp;
		}
		HandleFlowDir = MaxFlowDir;
		MaxFlowDir = MaxFlowDir + 1;
	}

	/* Creation d'une nouvelle grille d'ÃƒÂ©coulement rattachÃƒÂ© au handle */

	if (!(ptr_FlowDirGrid[HandleFlowDir] = (FlowDirGrid *) malloc(sizeof(FlowDirGrid))))
	{
		SetErreur("CreateFlowDirGrid (%s) - could not allocate flow direction structure\n", Nom);
		return(-1);
	}

	F = ptr_FlowDirGrid[HandleFlowDir];

        /* Initialisations du pointeur sur les donnÃƒÂ©es
	   (nÃƒÂ©cessaire au cas oÃƒÂ¹ l'on devrait dÃƒÂ©truire de modÃƒÂ¨le avant la fin de sa constition) */

	F->Data = NULL;

	strncpy(F->Nom, Nom, 120);
        F->Header.ncols = ncols;
        F->Header.nrows = nrows;
        F->Header.cellsize = cellsize;
        F->Header.xll = xll;
        F->Header.yll = yll;
        F->Header.NODATA_value = NODATA_value;
        F->Header.LL = LL;
      
        F->Compass[0][0] = 0;
        F->Compass[0][1] = 1;
        F->Compass[0][2] = 2;
        F->Compass[1][0] = 3;
        F->Compass[1][1] = 4;
        F->Compass[1][2] = 5;
        F->Compass[2][0] = 6;
        F->Compass[2][1] = 7;
        F->Compass[2][2] = 8;

        if (  AllocateFlowDirGrid(F) )
        {
            SetErreur("CreateFlowDirGrid (%s) : could not allocate memory for flow direction data\n", Nom);
            DestroyFlowDirGrid(HandleFlowDir);
            return(-1);
        }

        return(HandleFlowDir);
}

int DestroyFlowDirGrid (const int HandleFlowDir)
{
	FlowDirGrid *F;

	if( !(F = GetFlowDirGrid(HandleFlowDir)) )
	{
		SetErreur("DestroyFlowDirGrid : flow direction grid %d does not exist\n", HandleFlowDir);
		return(-1);
	}

	FreeFlowDirGrid(F);
	free(ptr_FlowDirGrid[HandleFlowDir]);
	ptr_FlowDirGrid[HandleFlowDir] = NULL;

	return(0);
}

FlowDirGrid * GetFlowDirGrid (const int HandleFlowDir)
{
		if ((HandleFlowDir < 0) | (HandleFlowDir >= MaxFlowDir)) return(NULL);
                else return(ptr_FlowDirGrid[HandleFlowDir]);
}

int AllocateFlowDirGrid (  FlowDirGrid * Grid )
{
    int ncols,nrows,i;

    ncols = Grid->Header.ncols;
    nrows = Grid->Header.nrows;

    /* Allocation du tableau de pointeurs sur les dÃ©buts de lignes */

    if( !(Grid->Data = (unsigned char **) malloc(sizeof(unsigned char *)*nrows) ) )
    {
        SetErreur("AllocateFlowDirGrid : could not allocate pointer for rows\n");
        return(-1);
    }

    /* Allocation des lignes successives */

    for(i=0;i<nrows;i++)
    {
        if ( !(Grid->Data[i] = (unsigned char *) malloc(sizeof(unsigned char)*ncols) ) )
        {
            SetErreur("AllocateFlowDirGrid : could not allocate row %d\n",i);
            return(-1);
        }
    }

    return(0);

}

void FreeFlowDirGrid (  FlowDirGrid * Grid )
{
    int i,nrows;

    nrows = Grid->Header.nrows;

    /* LibÃ©ration des lignes */

    for(i=0;i<nrows;i++)
        free(Grid->Data[i]);

    /* LibÃ©ration du tableau de pointeurs des dÃ©buts de lignes */

    free(Grid->Data);
}

int CreateFlowDirGridFromBinaryFile (   const char Nom[120] ,
                                            const char _binfile[200] ,
                                            const char _hdrfile[200] ,
                                            const int TYPE_SRC ,
                                            const float Compass[3][3]
                                         )
{
    int HandleFlowDir,ncols,nrows,nc,nr,nrec,LL;
    double cellsize,xll,yll;
    float NODATA_value,f;
    unsigned char uc;
    char binfile[200], hdrfile[200];
    bool SWITCH_ENDIANNESS;
    char LSBFIRST;
    FlowDirGrid * F;
    map<float,unsigned char>MapCompass;
    map<float,unsigned char>::iterator imuu;

    /* Protection des chaines de caractÃƒÂ¨res */
    
    strncpy(binfile, _binfile, 200);
    strncpy(hdrfile, _hdrfile, 200);

    /* Lecture du fichier d'entete */

    if ( ReadHeaderFromFile ( hdrfile , & ncols , & nrows , & xll , & yll , & cellsize , & NODATA_value , & LL , & LSBFIRST) )
    {
        SetErreur("CreateFloatRasterFromBinaryFile : could not read header file (%s)\n",hdrfile);
        return(-1);
    }

    if ( ( (LSBFIRST != 0) & !SYSTEM_IS_BIG_ENDIAN() ) |
         ( (LSBFIRST == 0) &  SYSTEM_IS_BIG_ENDIAN() )      )
    {
        SWITCH_ENDIANNESS = true;
    }
    else
    {
        SWITCH_ENDIANNESS = false;
    }

    /* CrÃƒÂ©ation du raster */

    HandleFlowDir = CreateFlowDirGrid ( Nom, ncols, nrows, cellsize, xll, yll, NODATA_value, LL );

    if(HandleFlowDir<0)
    {
        SetErreur("CreateFlowDirGridFromBinaryFile : could not create flow direction grid\n");
        return(-1);
    }

    F = GetFlowDirGrid(HandleFlowDir);

    /* Creation de la table de conversion du codage des directions d'ecoulement */
    
    MapCompass.clear();
    uc=0;

    for (nr=0;nr<3;nr++)
    {
        for (nc=0;nc<3;nc++)
        {
            MapCompass.insert(make_pair<float,unsigned char>(Compass[nr][nc],uc));
            uc++;
        }
    }

    /* Lecture des donnÃƒÂ©es */

    ifstream bin (binfile,ios::in|ios::binary);

    if (!bin)
    {
        SetErreur("CreateFlowDirGridFromBinaryFile : could not open binary file\n");
        DestroyFlowDirGrid(HandleFlowDir);
        return(-1);
    }

    nrec = 0;

    for (nr=0;nr<nrows;nr++)
    {
        for (nc=0;nc<ncols;nc++)
        {
                ReadFloat(bin,TYPE_SRC,SWITCH_ENDIANNESS,&f);
				if (f == NODATA_value)
					f = Compass[1][1];
				
                if ( !(bin.eof()) )
                {
                    imuu = MapCompass.find(f);
                    F->Data[nr][nc] = (*imuu).second;
                }
                else
                {
                    DestroyFlowDirGrid(HandleFlowDir);
                    SetErreur("CreateFlowDirGridFromBinaryFile : file contains less records than required to fill the grid!\n");
                    bin.close();
                    return(-1);
                }
        }
    }

    ReadUnsignedChar(bin,TYPE_SRC,SWITCH_ENDIANNESS,&uc);

    if ( !(bin.eof()) )
    {
        DestroyFlowDirGrid(HandleFlowDir);
        SetErreur("CreateFlowDirGridFromBinaryFile : file contains more records than required to fill the grid!\n");
        bin.close();
        return(-1);
    }

    bin.close();
    return(HandleFlowDir);
}

int GetFlowDirGridHeader (  int HandleFlowDir ,
                            int * _ncols ,
                            int * _nrows ,
                            double * _cellsize ,
                            double * _xll ,
                            double * _yll ,
                            float * _NODATA_value ,
                            int * _LL
                          )
{
    FlowDirGrid * F;

    if ( !(F = GetFlowDirGrid(HandleFlowDir)) )
    {
        SetErreur("GetFlowDirGridHeader : flow direction grid %d does not exist\n",HandleFlowDir);
        return(-1);
    }

    *_ncols = F->Header.ncols;
    *_nrows = F->Header.nrows;
    *_cellsize = F->Header.cellsize;
    *_xll = F->Header.xll;
    *_yll = F->Header.yll;
    *_NODATA_value = F->Header.NODATA_value;
    *_LL = F->Header.LL;

    return(0);
}

int WriteFlowDirGridToFile (    const int HandleFlowDir ,
                                const char _binfile[200] ,
                                const int TYPE_DST,
                                const int SWITCH_ENDIANNESS
                            )
{
    int ncols,nrows,nc,nr;
    char binfile[120];
    unsigned char u;
    FlowDirGrid * F;

    /* Protection des chaines de caracteres */

    strncpy(binfile, _binfile, 120);

    if ( !(F = GetFlowDirGrid(HandleFlowDir)) )
    {
        SetErreur("WriteFlowDirGridToFile : flow direction grid %d does not exist\n",HandleFlowDir);
        return(-1);
    }

    ofstream bin (binfile,ios::out|ios::binary);

    if (!bin)
    {
        SetErreur("WriteFlowDirGridToFile : could not open output file\n");
        return(-1);
    }

	nrows = F->Header.nrows;
	ncols = F->Header.ncols;
	
    for (nr=0;nr<nrows;nr++)
    {
        for (nc=0;nc<ncols;nc++)
        {
            u = F->Data[nr][nc];
            WriteUnsignedChar(bin,TYPE_DST,(bool)SWITCH_ENDIANNESS,&u);
        }
    }

	bin.close();
	return(0);
	
}


int PutFlowDirValue ( 	const int HandleFlowDir ,
						int ncells ,
						int * CELLS ,
						float * VALUES,
						float Compass[3][3]
                    )
{

    int i,nr,nc;
	unsigned char uc;
	float f;
    FlowDirGrid * F;
    map<float,unsigned char>MapCompass;
    map<float,unsigned char>::iterator imfu;
	
    if ( !(F = GetFlowDirGrid(HandleFlowDir)))
    {
        SetErreur("PutFlowDirValue : flow direction grid # %d does not exist\n",HandleFlowDir);
        return(-1);
    }

	/* Creation de la table de conversion du codage des directions d'ecoulement */
    
    MapCompass.clear();
    uc=0;

    for (nr=0;nr<3;nr++)
    {
        for (nc=0;nc<3;nc++)
        {
            MapCompass.insert(make_pair<float,unsigned char>(Compass[nr][nc],uc));
            uc++;
        }
    }
	
	
    for (i=0;i<ncells;i++)
    {
        (void) rec2RC ( &(F->Header) , CELLS + i , & nr, & nc );
		f = VALUES[i];
		imfu = MapCompass.find(f);
		
		if ( imfu == MapCompass.end() )
		{
			SetErreur("PutFlowDirValue : record value (%f) does not appear in the correspondance table and cannot be associated with a direction\n",f);
			return(-1);
		}
		
        F->Data[nr][nc] = (*imfu).second;
		
    }

    return(0);
}



int GetFlowDirValue ( const int HandleFlowDir , const int ncells , int * CELLS , const float Compass[3][3] , float ** _VALUES )
{
    int i,nr,nc;
	unsigned char uc;
    FlowDirGrid * F;
    map<unsigned char,float>MapCompass;
    map<unsigned char,float>::iterator imuf;

    if ( !(F = GetFlowDirGrid(HandleFlowDir)))
    {
        SetErreur("GetlowDirValue : flow direction grid # %d does not exist\n",HandleFlowDir);
        return(-1);
    }

	/* Creation de la table de conversion du codage des directions d'ecoulement */
    
    MapCompass.clear();
    uc=0;

    for (nr=0;nr<3;nr++)
    {
        for (nc=0;nc<3;nc++)
        {
            MapCompass.insert( make_pair<unsigned char,float>(uc,Compass[nr][nc]) );
            uc++;
        }
    }
	
    for (i=0;i<ncells;i++)
    {
        (void) rec2RC ( &(F->Header) , CELLS + i , & nr, & nc );
        uc = F->Data[nr][nc];
		imuf = MapCompass.find(uc);	
		(*_VALUES)[i] = (*imuf).second;
    }
    
    return(0);
}


int CropFlowDir ( int HandleFlowDir, int startrow , int startcol , int endrow , int endcol, char Nom[120] )
{
    FlowDirGrid * F , * Fc ;
    int nrows1 , ncols1 , LL1 , nr , nc ;
    double xll1, yll1, cellsize1 ;
    float NODATA_value1 ;
    int HandleCroppedFlowDir ;

    if ( !(F = GetFlowDirGrid(HandleFlowDir)) )
    {
        SetErreur("CropFlowDir : flow direction grid %d does not exist\n",HandleFlowDir);
        return(-1);
    }

//    (void) xy2RC ( &(F->Header) , xmin , ymin , & endrow , & startcol ) ;
//    (void) xy2RC ( &(F->Header) , xmax , ymax , & startrow , & endcol ) ;

    if (startrow<0)
        startrow = 0;

    if (endrow>=F->Header.nrows)
        endrow = (F->Header.nrows)-1;

    if (startcol<0)
        startcol = 0;

    if (endcol>=F->Header.ncols)
        endcol = (F->Header.ncols)-1;

    // Calcul du nouvel entÃªte

    nrows1 = endrow - startrow + 1 ;
    ncols1 = endcol - startcol + 1 ;

    cellsize1 = F->Header.cellsize ;
    xll1 = F->Header.xll + startcol * cellsize1 ;
    yll1 = F->Header.yll + (F->Header.nrows - 1 - endrow) * cellsize1 ;
    LL1 = F->Header.LL ;
    NODATA_value1 = F->Header.NODATA_value ;

    // CrÃ©ation de la grille recadrÃ©e

    HandleCroppedFlowDir = CreateFlowDirGrid ( Nom , ncols1 , nrows1 , cellsize1 , xll1 , yll1 , NODATA_value1 , LL1 ) ;

    if ( !(Fc = GetFlowDirGrid(HandleCroppedFlowDir)) )
    {
        SetErreur("CropFlowDir : could not get pointer on new grid\n");
        return(-1);
    }

    // Recopie des conventions de directions

    for (nr=0;nr<3;nr++)
    {
        for ( nc=0;nc<3;nc++)
        {
            Fc->Compass[nr][nc] = F->Compass[nr][nc] ;
        }
    }

    // Recopie des valeurs

    for (nr=0;nr<nrows1;nr++)
    {
        for ( nc=0;nc<ncols1;nc++)
        {
            Fc->Data[nr][nc] = F->Data[nr+startrow][nc+startcol] ;
        }
    }

    return(HandleCroppedFlowDir) ;
}



int SetFlowDirToNoFlow ( int HandleFlowDir )
{
    FlowDirGrid * F ;
    int nr,nc;

    if ( !(F = GetFlowDirGrid(HandleFlowDir)) )
    {
        SetErreur("SetFlowDirToNoFlow : flow direction grid %d does not exist\n",HandleFlowDir);
        return(-1);
    }

    for (nr=0 ; nr<F->Header.nrows ; nr++ )
    {
        for (nc=0 ; nc<F->Header.ncols ; nc++ )
        {
            F->Data[nr][nc] = 4;
        }
    }

    return(0);
}
