/* 
* 
* 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 "FloatRaster.h"
#include "esriheader.h"
#include "Erreur.h"
#include "InOut.h"

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

using namespace std;

extern FloatRaster ** ptr_FloatRaster = NULL;
extern int MaxRaster = 0;

int CreateFloatRaster (   char Nom[120],
                          int ncols,
                          int nrows,
                          double cellsize,
                          double xll,
                          double yll,
                          float NODATA_value,
                          int LL
                      )
{
	int HandleRaster = MaxRaster;
	FloatRaster **ptr_FloatRaster_tmp;
	FloatRaster *R;

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

	ClearErreur();

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

	HandleRaster = MaxRaster - 1;

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

	if(HandleRaster < 0)							/* Pas de handle disponible */
	{
		if (MaxRaster == 0)
		{
			ptr_FloatRaster = (FloatRaster **) malloc((MaxRaster + 1) * sizeof(FloatRaster *));
			if (ptr_FloatRaster == NULL)
			{
				SetErreur("CreateFloatRaster (%s) - could not allocate handle\n", Nom);
				return(-1);
			}
		}
		else
		{
			ptr_FloatRaster_tmp = (FloatRaster **) realloc(ptr_FloatRaster, (MaxRaster + 1) * sizeof(FloatRaster *));
			if (ptr_FloatRaster_tmp == NULL)
			{
				SetErreur("CreateFloatRaster (%s) - could not allocate handle\n", Nom);
				return(-1);
			}
			else ptr_FloatRaster = ptr_FloatRaster_tmp;
		}
		HandleRaster = MaxRaster;
		MaxRaster = MaxRaster + 1;
	}

	/* Creation d'un nouveau raster rattachÃ© au handle */

	if (!(ptr_FloatRaster[HandleRaster] = (FloatRaster *) malloc(sizeof(FloatRaster))))
	{
		SetErreur("CreateFloatRaster (%s) - could not allocate raster structure\n", Nom);
		return(-1);
	}

	R = ptr_FloatRaster[HandleRaster];

        /* 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) */

	R->Data = NULL;

	strncpy(R->Nom, Nom, 120);
        R->Header.ncols = ncols;
        R->Header.nrows = nrows;
        R->Header.cellsize = cellsize;
        R->Header.xll = xll;
        R->Header.yll = yll;
        R->Header.NODATA_value = NODATA_value;
        R->Header.LL = LL;

        if (  AllocateFloatRaster(R) )
        {
            SetErreur("CreateFloatRaster (%s) : could not allocate memory for raster data\n", Nom);
            DestroyFloatRaster(HandleRaster);
            return(-1);
        }

        return(HandleRaster);
}

int DestroyFloatRaster (const int HandleRaster)
{
	FloatRaster *R;

	if( !(R = GetFloatRaster(HandleRaster)) )
	{
            SetErreur("DestroyFloatRaster : raster %d does not exist\n", HandleRaster);
            return(-1);
	}

	FreeFloatRaster(R);
	free(ptr_FloatRaster[HandleRaster]);
	ptr_FloatRaster[HandleRaster] = NULL;

	return(0);
}

FloatRaster * GetFloatRaster (const int HandleRaster)
{
		if ((HandleRaster < 0) | (HandleRaster >= MaxRaster)) return(NULL);
                else return(ptr_FloatRaster[HandleRaster]);
}

int AllocateFloatRaster (  FloatRaster * 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 = (float **) malloc(sizeof(float *)*nrows) ) )
    {
        SetErreur("AllocateFloatRaster : could not allocate pointer for rows\n");
        return(-1);
    }

    /* Allocation des lignes successives */

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

    return(0);
    
}

void FreeFloatRaster (  FloatRaster * 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 CreateFloatRasterFromBinaryFile (   char Nom[120] ,
                                        char _binfile[200] ,
                                        char _hdrfile[200] ,
                                        int TYPE_SRC
                                     )
{
    int HandleRaster,ncols,nrows,nc,nr,nrec,LL;
    double cellsize,xll,yll;
    float NODATA_value,f;
    char binfile[200], hdrfile[200];
    char LSBFIRST;
    bool SWITCH_ENDIANNESS;
    FloatRaster * R;

    /* 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 */

    HandleRaster = CreateFloatRaster ( Nom, ncols, nrows, cellsize, xll, yll, NODATA_value, LL);

    if(HandleRaster<0)
    {
        SetErreur("CreateFloatRasterFromBinaryFile : could not create raster\n");
        return(-1);
    }

    R = GetFloatRaster(HandleRaster);

    /* Lecture des donnÃ©es */

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

    if (!bin)
    {
        SetErreur("CreateFloatRasterFromBinaryFile : could not open binary file\n");
        DestroyFloatRaster(HandleRaster);
        return(-1);
    }

    nrec = 0;

    for (nr=0;nr<nrows;nr++)
    {
        for (nc=0;nc<ncols;nc++)
        {
                ReadFloat(bin,TYPE_SRC,SWITCH_ENDIANNESS,&f);
                
                if ( !(bin.eof()) )
                {
                    R->Data[nr][nc] = f;
                }
                else
                {
                    DestroyFloatRaster(HandleRaster);
                    SetErreur("CreateFloatRasterFromBinaryFile : file contains less records than required to fill the raster!\n");
                    bin.close();
                    return(-1);
                }
        }
    }

    ReadFloat(bin,TYPE_SRC,SWITCH_ENDIANNESS,&f);

    if ( !(bin.eof()) )
    {
        DestroyFloatRaster(HandleRaster);
        SetErreur("CreateFloatRasterFromBinaryFile : file contains more records than required to fill the raster!\n");
        bin.close();
        return(-1);
    }

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

int CreateFloatRasterFromAsciiFile (    const char Nom[120] ,
                                        const char ascfile[200] ,
                                        int LL
                                    )
{

}

int CreateFloatRasterFromPointer (  const char Nom[120] ,
                                    const float * Data ,
                                    const int ncols,
                                    const int nrows,
                                    const float cellsize,
                                    const float xll,
                                    const float yll,
                                    const float NODATA_value,
                                    int LL
                                  )
{
    
}

int GetFloatRasterHeader (  int HandleRaster ,
                            int * _ncols ,
                            int * _nrows ,
                            double * _cellsize ,
                            double * _xll ,
                            double * _yll ,
                            float * _NODATA_value ,
                            int * _LL
                          )
{
    FloatRaster * R;

    if ( !(R = GetFloatRaster(HandleRaster)) )
    {
        SetErreur("GetFloatRasterHeader : raster %d does not exist\n",HandleRaster);
        return(-1);
    }

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

    return(0);
}

int GetFloatRasterData ( int HandleRaster , float ** _Data )
{
    int nrows, ncols, nr, nc;
    FloatRaster * R;

    if ( !(R = GetFloatRaster(HandleRaster)) )
    {
        SetErreur("GetFloatRasterData : raster %d does not exist\n",HandleRaster);
        return(-1);
    }

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

    for(nr=0;nr<nrows;nr++)
    {
        for(nc=0;nc<ncols;nc++)
        {
            /* Copie et transposition dans le tableau ("1D") */

            (*_Data)[nr+nc*nrows] = R->Data[nr][nc];

        }
    }

    return(0);   
}

int SetRasterToNODATA ( const int HandleRaster )
{
    int nrows, ncols, nr, nc;
    FloatRaster * R;

    if ( !(R = GetFloatRaster(HandleRaster)) )
    {
        SetErreur("SetRasterToNODATA : raster %d does not exist\n",HandleRaster);
        return(-1);
    }

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

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

    return(0);
}

int SetRasterToConstant ( const int HandleRaster , const float constant )
{
    int nrows, ncols, nr, nc;
    FloatRaster * R;

    if ( !(R = GetFloatRaster(HandleRaster)) )
    {
        SetErreur("SetRasterToNODATA : raster %d does not exist\n",HandleRaster);
        return(-1);
    }

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

    for(nr=0;nr<nrows;nr++)
    {
        for(nc=0;nc<ncols;nc++)
            R->Data[nr][nc] = constant ;
    }

    return(0);
}

int WriteFloatRasterToFile (    const int HandleRaster ,
                                const char _binfile[200] ,
                                const int TYPE_DST,
                                const int SWITCH_ENDIANNESS
                            )
{
    int ncols,nrows,nc,nr;
    char binfile[120];
    float f;
    FloatRaster * R;

    /* Protection des chaines de caracteres */

    strncpy(binfile, _binfile, 120);

    if ( !(R = GetFloatRaster(HandleRaster)) )
    {
        SetErreur("WriteFloatRasterToFile : raster %d does not exist\n",HandleRaster);
        return(-1);
    }

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

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

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

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

int CropRaster ( int HandleRaster, int startrow , int startcol , int endrow , int endcol, char Nom[120] )
{
    FloatRaster * R , * Rc ;
    int nrows1 , ncols1 , LL1 , nr , nc ;
    double xll1, yll1, cellsize1 ;
    float NODATA_value1 ;

    int HandleCroppedRaster ;

    if ( !(R = GetFloatRaster(HandleRaster)) )
    {
        SetErreur("CropRaster : raster %d does not exist\n",HandleRaster);
        return(-1);
    }

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

    if (startrow<0)
        startrow = 0;

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

    if (startcol<0)
        startcol = 0;

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

    // Calcul du nouvel entête

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

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

    // Création du raster recadré

    HandleCroppedRaster = CreateFloatRaster ( Nom , ncols1 , nrows1 , cellsize1 , xll1 , yll1 , NODATA_value1 , LL1 ) ;

    if ( !(Rc = GetFloatRaster(HandleCroppedRaster)) )
    {
        SetErreur("CropRaster : could not get pointer on new raster\n");
        return(-1);
    }

    // Recopie des valeurs

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

    return(HandleCroppedRaster) ;
}
