/**********************************************************
 * Version $Id$
 *********************************************************/
/*******************************************************************************
    IsochronesVar.cpp
    Copyright (C) Victor Olaya

    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, 5th Floor, Boston, MA 02110-1301, USA
*******************************************************************************/

//-----------------------------------------------------
#include "IsochronesVar.h"
#include "Helper.h"

//-----------------------------------------------------
#ifndef max
#define max(a,b) (((a) > (b)) ? (a) : (b))
#endif

//-----------------------------------------------------
CIsochronesVar::CIsochronesVar(void){

	Set_Name		(_TL("Isochrones Variable Speed"));

	Set_Author		("V.Olaya (c) 2004, V.Wichmann (c) 2015");

	Set_Description	(_TW(
		"Calculation of isochrones with variable speed.\n"
		"In case a cell in an optional input grid is NoData, the corresponding parameter value will "
		"be used instead of skipping this cell.\n\n"
	));

	Add_Reference("Al-Smadi, Mohammad", "1998",
		"Incorporating spatial and temporal variation of watershed response in a gis-based hydrologic model",
		"Faculty of the Virginia Polythecnic Insitute and State University. MsC Thesis.",
		SG_T("scholar.lib.vt.edu/theses/available/etd-121698-112858/unrestricted/smadi.pdf"),
		SG_T("online")
	);

	Add_Reference("Martínez Álvarez, V.; Dal-Ré Tenreiro, R.; García García, A. I.; Ayuga Téllez, F.", "",
		"Modelación distribuida de la escorrentía superficial en pequeñas cuencas mediante SIG",
		"Evaluación experimental."
	);

	Add_Reference("Olaya, V.", "2004",
		"Hidrologia computacional y modelos digitales del terreno",
		"Alqua. 536 pp."
	);

	//-------------------------------------------------
	Parameters.Add_Grid("",
		"DEM", _TL("Elevation"),
		_TL(""),
		PARAMETER_INPUT
	);

	Parameters.Add_Grid("",
		"SLOPE", _TL("Slope"),
		_TL(""),
		PARAMETER_INPUT
	);

	Parameters.Add_Grid("",
		"FLOWACC", _TL("Catchment Area"),
		_TL(""),
		PARAMETER_INPUT
	);

	Parameters.Add_Grid("",
		"CN", _TL("Curve Number"),
		_TL(""),
		PARAMETER_INPUT_OPTIONAL
	);

	Parameters.Add_Grid("",
		"MANNING", _TL("Manning's N"),
		_TL(""),
		PARAMETER_INPUT_OPTIONAL
	);

	Parameters.Add_Grid("",
		"TIME", _TL("Time Out(h)"),
		_TL(""),
		PARAMETER_OUTPUT, true, SG_DATATYPE_Double
	);

	Parameters.Add_Grid("",
		"SPEED", _TL("Speed (m/s)"),
		_TL(""),
		PARAMETER_OUTPUT, true, SG_DATATYPE_Double
	);

	//-------------------------------------------------
	Parameters.Add_Double("",
		"AVGMANNING", _TL("Avg. Manning's N"),
		_TL(""),
		0.15
	);

	Parameters.Add_Double("",
		"AVGCN", _TL("Avg. Curve Number"),
		_TL(""),
		75
	);

	Parameters.Add_Double("",
		"THRSMIXED", _TL("Mixed Flow Threshold (ha)"),
		_TL(""),
		18
	);

	Parameters.Add_Double("",
		"THRSCHANNEL", _TL("Channel Definition Threshold (ha)"),
		_TL(""),
		360
	);

	Parameters.Add_Double("",
		"AVGRAINFALL", _TL("Avg. Rainfall Intensity (mm/h)"),
		_TL(""),
		1
	);

	Parameters.Add_Double("",
		"CHANSLOPE", _TL("Channel side slope(m/m)"),
		_TL(""),
		0.5
	);

	Parameters.Add_Double("",
		"MINSPEED", _TL("Min. Flow Speed (m/s)"),
		_TL(""),
		0.05
	);
}

//-----------------------------------------------------
CIsochronesVar::~CIsochronesVar(void)
{
	Execute_Finish();
}

//-----------------------------------------------------
void CIsochronesVar::_CalculateTime(int x, int y)
{
	CSG_Grid_Stack	Stack;
	double	dDist = 1;
    double	dD = 0;
    double	dSlope;
    double	dSpeed;
    double	dQ = 0;
    double	dH;
    double	dSup;
    double	dInf;
    double	dAcc;
    double	dArea = 0;
    double	dPerim;
    double	dDif;
    double	dManning;
	double	dCN;
    double	dI = 0;
	int		iIter = 0;
	int		iDir;


	Stack.Push(x, y);

	//-----------------------------------------------------
	while( Stack.Get_Size() > 0 && Process_Get_Okay() )
	{
		Stack.Pop(x, y);

		if (m_pCN != NULL && !m_pCN->is_NoData(x, y))
		{
			dCN = m_pCN->asDouble(x, y);
		}
		else
		{
			dCN = m_dCN;
		}

		dI = Runoff(m_dRainfall, dCN);
		dI /= 3600.0;	// in mm/s
        dI /= 1000.0;	// m/s of runoff;

		iDir  = m_Direction.asInt(x, y);
		dDist = Get_Length(iDir);	// length to previous, i.e. successor cell

		dSlope = m_pSlope->asDouble(x, y);
		dSlope = fabs(tan(dSlope));
		dSlope = max(0.001, dSlope);

		dAcc = m_pCatchArea->asDouble(x, y);

        if (dAcc < m_dMixedThresh)
		{
            dD = sqrt(2.0 * dAcc / 3.14159);
			
			if (m_pManning != NULL && !m_pManning->is_NoData(x, y))
			{
				dManning = m_pManning->asDouble(x, y);
			}
			else
			{
				dManning = m_dManning;
			}

            dSpeed = max(m_dMinSpeed, pow(dI * dD, 0.4) * pow(dSlope, 0.3) / pow(dManning, 0.6));
        }
        else
		{
			if (dAcc < m_dChannelThresh)
			{
				dManning = 0.06;
			}
			else
			{
				dManning= 0.05;
			}

            dQ		= dI * dAcc; // Q m3/s
            dSup	= 60;
            dInf	= 0;
            dH		= 2;
            dArea	= dH * dH / m_dChannelSlope;
            dPerim	= 2.0 * (dH / m_dChannelSlope + sqrt(dH * dH + pow(dH / m_dChannelSlope, 2.0)));
            dDif	= (sqrt(dSlope) * pow(dArea, 5.0 / 3.0) / pow(dPerim, 2.0 / 3.0) / dManning) - dQ;
            iIter	= 0;

			do
			{
                if (dDif > 0)
				{
                    dSup	= dH;
                    dH		= (dInf + dH) / 2.0;
                }
                else if (dDif < 0)
				{
                    dInf	= dH;
                    dH		= (dSup + dH) / 2.0;
                }

                dArea	= dH * dH / m_dChannelSlope;
                dPerim	= 2.0 * (dH / m_dChannelSlope + sqrt(dH * dH + pow(dH / m_dChannelSlope, 2.0)));
                dDif	= (sqrt(dSlope) * pow(dArea, 5.0 / 3.0) / pow(dPerim, 2.0 / 3.0) / dManning) - dQ;
				iIter++;
            }
			while (fabs(dDif) > 0.1);

            dSpeed = max(m_dMinSpeed, dQ / dArea);
        }

		int ix = Get_xTo(iDir, x);
		int iy = Get_yTo(iDir, y);

		m_pTime->Set_Value(x, y, m_pTime->asDouble(ix, iy) + dDist / dSpeed);
		m_pSpeed->Set_Value(x, y, dSpeed);

		//-------------------------------------------------
		for(int i=0; i<8; i++)
		{
			ix	= Get_xFrom(i, x);
			iy	= Get_yFrom(i, y);

			if( m_pDEM->is_InGrid(ix, iy) && i == m_Direction.asInt(ix, iy) )
			{
				Stack.Push(ix, iy);
			}
		}
	}

	return;
}


//-----------------------------------------------------
double CIsochronesVar::Runoff(
        double dRainfall,
        double dCN) {

    double dS;
    double dRunoff;

    dS = (25400.0 / dCN) - 254;

    if (dRainfall < (0.2 * dS)) {
        return 0.1; //can´t be 0, because that would mean speed = 0
    }// if

    dRunoff = pow(dRainfall - 0.2 * dS, 2)
            / (dRainfall + 0.8 * dS);

    return dRunoff;

}// method


//-----------------------------------------------------
void CIsochronesVar::ZeroToNoData(void){


    for(int y=0; y<Get_NY() && Set_Progress(y); y++)
	{
		#pragma omp parallel for
		for(int x=0; x<Get_NX(); x++)
		{
            if (m_pTime->asDouble(x, y) == 0)
			{
				m_pTime->Set_NoData(x, y);
			}

            if (m_pSpeed->asDouble(x, y) == 0)
			{
				m_pSpeed->Set_NoData(x, y);
			}
        }// for
    }// for

}//method


//-----------------------------------------------------
bool CIsochronesVar::On_Execute(void){

	m_pDEM = Parameters("DEM")->asGrid();
	m_pTime = Parameters("TIME")->asGrid();
	m_pSpeed = Parameters("SPEED")->asGrid();
	m_pManning = Parameters("MANNING")->asGrid();
	m_pSlope = Parameters("SLOPE")->asGrid();
	m_pCatchArea = Parameters("FLOWACC")->asGrid();
	m_pCN = Parameters("CN")->asGrid();
	m_dMixedThresh = Parameters("THRSMIXED")->asDouble() * 10000;
	m_dChannelThresh = Parameters("THRSCHANNEL")->asDouble() * 10000;
	m_dCN = Parameters("AVGCN")->asDouble();
	m_dManning = Parameters("AVGMANNING")->asDouble();
	m_dRainfall = Parameters("AVGRAINFALL")->asDouble();
	m_dChannelSlope = Parameters("CHANSLOPE")->asDouble();
	m_dMinSpeed = Parameters("MINSPEED")->asDouble();

	m_pTime->Assign(0.0);

	m_Direction.Create(Get_System(), SG_DATATYPE_Char);
	m_Direction.Set_NoData_Value(-1);

	Init_FlowDirectionsD8(m_pDEM, &m_Direction);

	return( true );

}//method


//-----------------------------------------------------
bool CIsochronesVar::On_Execute_Finish()
{
	m_Direction.Destroy();

	return( true );
}


//-----------------------------------------------------
bool CIsochronesVar::On_Execute_Position(CSG_Point ptWorld, TSG_Tool_Interactive_Mode Mode)
{
	int iX, iY;

	if(	Mode != TOOL_INTERACTIVE_LDOWN || !Get_Grid_Pos(iX, iY) )
	{
		return( false );
	}
	
	m_pTime->Assign(0.0);

	_CalculateTime(iX, iY);

	for(int y=0; y<Get_NY() && Set_Progress(y); y++)
	{
		#pragma omp parallel for
		for(int x=0; x<Get_NX(); x++)
		{
			m_pTime->Set_Value(x, y, m_pTime->asDouble(x,y)/3600.0);
        }
    }

	ZeroToNoData();

	DataObject_Update(m_pTime, SG_UI_DATAOBJECT_SHOW_LAST_MAP);

	return( true );

}//method
