/**********************************************************************************************
    Copyright (C) 2021 Oliver Eichler <oliver.eichler@gmx.de>

    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 3 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, see <http://www.gnu.org/licenses/>.

**********************************************************************************************/

#include "gis/proj_x.h"
#include <QDebug>
#include <QPolygonF>

CProj::CProj(const QString& crsSrc, const QString& crsTar)
{
    init(crsSrc.toLatin1(), crsTar.toLatin1());
}

CProj::~CProj()
{
    if(nullptr != _pj)
    {
        proj_destroy(_pj);
    }

    if(nullptr != _ctx)
    {
        proj_context_destroy(_ctx);
    }
}


void CProj::init(const char *crsSrc, const char *crsTar)
{
    _strProjSrc = crsSrc;
    _strProjTar = crsTar;

    _strProjSrc = _strProjSrc.remove("+towgs84=0,0,0,0,0,0,0,0");
    _strProjTar = _strProjTar.remove("+towgs84=0,0,0,0,0,0,0,0");

    if(!_strProjSrc.contains("EPSG") && !_strProjSrc.contains("+type=crs"))
    {
        _strProjSrc += " +type=crs";
    }
    if(!_strProjTar.contains("EPSG") && !_strProjTar.contains("+type=crs"))
    {
        _strProjTar += " +type=crs";
    }

    if(nullptr != _pj)
    {
        proj_destroy(_pj);
        _pj = nullptr;
    }

    if(nullptr != _ctx)
    {
        proj_context_destroy(_ctx);
        _ctx = nullptr;
    }


    _ctx = proj_context_create();
    if(nullptr == _ctx)
    {
        qWarning() << "Failed to create projection constex:";
        return;
    }
    _pj = proj_create_crs_to_crs(_ctx, _strProjSrc.toLatin1(), _strProjTar.toLatin1(), NULL);
    if(nullptr != _pj)
    {
        PJ* P_for_GIS = proj_normalize_for_visualization(_ctx, _pj);
        proj_destroy(_pj);
        _pj = P_for_GIS;
    }

    _isSrcLatLong = _isLatLong(_strProjSrc);
    _isTarLatLong = _isLatLong(_strProjTar);

    if (nullptr == _pj)
    {
        qWarning() << "Failed to create projection:" << _strProjSrc << "->" << _strProjTar;
        return;
    }

    qDebug() << "Create projection:" << _strProjSrc << "->" << _strProjTar;
}

bool CProj::_isLatLong(const QString &crs) const
{
    PJ * p = proj_create(_ctx, crs.toLatin1());
    PJ_TYPE type = proj_get_type(p);
    proj_destroy(p);

    return PJ_TYPE_GEOGRAPHIC_2D_CRS == type;
}

void CProj::transform(QPolygonF& line, PJ_DIRECTION dir) const
{
    if(!isValid())
    {
        return;
    }

    qreal factorPre = proj_degree_input(_pj, dir) ? RAD_TO_DEG : 1.0;
    qreal factorPost = proj_degree_output(_pj, dir) ? DEG_TO_RAD : 1.0;

    for(QPointF& pt : line)
    {
        pt *= factorPre;
        _transform(pt.rx(), pt.ry(), dir);
        pt *= factorPost;
    }
}

void CProj::transform(QPointF& pt, PJ_DIRECTION dir) const
{
    if(!isValid())
    {
        return;
    }

    if(proj_degree_input(_pj, dir))
    {
        pt *= RAD_TO_DEG;
    }

    _transform(pt.rx(), pt.ry(), dir);

    if(proj_degree_output(_pj, dir))
    {
        pt *= DEG_TO_RAD;
    }
}

void CProj::transform(qreal& lon, qreal& lat, PJ_DIRECTION dir) const
{
    if(!isValid())
    {
        return;
    }

    if(proj_degree_input(_pj, dir))
    {
        lon *= RAD_TO_DEG;
        lat *= RAD_TO_DEG;
    }

    _transform(lon, lat, dir);

    if(proj_degree_output(_pj, dir))
    {
        lon *= DEG_TO_RAD;
        lat *= DEG_TO_RAD;
    }
}

void CProj::_transform(qreal& lon, qreal& lat, PJ_DIRECTION dir) const
{
    PJ_COORD c = proj_coord(lon, lat, 0, 0);
    c = proj_trans(_pj, dir, c);
    lon = c.uv.u;
    lat = c.uv.v;
}

bool CProj::validProjStr(const QString projStr, bool allowLonLatToo, fErrMessage errMessage)
{
    bool res = false;

    PJ * projCheck = proj_create(PJ_DEFAULT_CTX, projStr.toLatin1());
    if (!projCheck)
    {
        if(errMessage)
        {
            errMessage(tr("The value\n'%1'\nis not a valid coordinate system definition:\n%2").arg(projStr, proj_errno_string(proj_context_errno(PJ_DEFAULT_CTX))));
        }
    }
    else
    {
        PJ_TYPE type = proj_get_type(projCheck);
        proj_destroy(projCheck);

        if(!allowLonLatToo && (PJ_TYPE_GEOGRAPHIC_2D_CRS == type))
        {
            if(errMessage)
            {
                errMessage(tr("Lat/Lon projection is not allowed in this case."));
            }
        }
        else
        {
            res = true;
        }
    }

    return res;
}
