/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include <iostream>
#include <sstream>

#include "Metview.h"

#ifndef MISSING_DATA
const double MISSING_DATA = 1E21;
#endif

double makeDateNumber(long date, long time);

class geopt
{
public:
    float latitude;
    float longitude;
    float height;
    long date;
    long time;
    double value;

    geopt()
    {
        latitude = 0;
        longitude = 0;
        height = 0;
        date = 0;
        time = 0;
        value = 0;
    }
    geopt& operator=(const geopt& in) = default;
    void load(const char* line);
    //{
    // sscanf(line,"%f %f %f %ld %ld %lf",
    //     &latitude, &longitude, &height, &date, &time, &value);
    //}
    void print()
    {
        printf("Latitude = %f Longitude = %f Value = %f\n", latitude, longitude, value);
    }
};

class ToMatrix
{
    geopt* Points{nullptr};
    int NbPoints{0};
    double North{0.};
    double South{0.};
    double West{0.};
    double East{0.};
    double GridLat{1.};
    double GridLon{1.};
    double Tolerance{0.};
    int NbLat{0};
    int NbLon{0};
    double Date{0.};
    double* Matrix{nullptr};

public:
    ToMatrix(MvRequest&);
    int load(const char*);
    int save(const char*);
    void estimate();
    double value(float lat, float lon);
    void sortPoints();
    void print()
    {
        printf("Print Matrix....\n");
        for (int i = 0; i < NbPoints; i++)
            Points[i].print();
    }

protected:
    float long180(float x) { return x > 180 ? x - 360 : x; }
    float long360(float x) { return x < 0 ? x + 360 : x; }
    float longDistance(float x1, float x2);
};

class GeoToMatrix : public MvService
{
public:
    GeoToMatrix() :
        MvService("GEO_TO_MATRIX") {}
    void serve(MvRequest&, MvRequest&);
};

double makeDateNumber(long date, long time)
{
    auto myDate = double(date);
    auto myTime = double(time);

    if (myDate < 470620)  //-- must be relative => no time
        return myDate;

    if (myTime < 0)  //-- illegal or rubbish
        return myDate;

    if (myTime > 2400)  //-- illegal or rubbish
        return myDate;

    if (myTime >= 100)  //-- 01:00...24:00
        return myDate + myTime / 2400.;

    if (myTime > 59)  //-- illegal or rubbish
        return myDate;

    if (myTime > 24)  //-- 00:25...00:59
        return myDate + myTime / 60. / 24.;

    return myDate + myTime / 24.;
}

void geopt::load(const char* line)
{
    double d_date;  //-- if user has floating point valued data here...
    double d_time;

    std::istringstream myInput(line);
    myInput >> latitude >> longitude >> height >> d_date >> d_time >> value;

    date = (long)d_date;
    time = (long)d_time;
}

ToMatrix::ToMatrix(MvRequest& def)
{
    North = def("AREA", 0);
    West = def("AREA", 1);
    South = def("AREA", 2);
    East = def("AREA", 3);
    GridLat = def("GRID");
    GridLon = def("GRID", 1);
    Tolerance = def("TOLERANCE");

    if (North < South) {
        double tmp = North;
        North = South;
        South = tmp;
    }

    if (!GridLat)
        GridLat = 1.5;
    if (!GridLon)
        GridLon = GridLat;


    MvRequest data;
    def.getValue(data, "GEOPOINTS");

    load(data("PATH"));
}

int ToMatrix::load(const char* path)
{
    FILE* f = fopen(path, "r");
    if (f == nullptr)
        return 1;

    // first count the lines
    char line[1024];
    int n = 0;

    while (fgets(line, sizeof(line), f))
        n++;


    Points = new geopt[n];

    n = 0;
    rewind(f);

    while (fgets(line, sizeof(line), f))
        if (strncmp(line, "#DATA", 5) == 0)
            break;

    while (fgets(line, sizeof(line), f)) {
        if (*line != '#') {
            Points[n].load(line);
            n++;
        }
    }
    NbPoints = n;
    fclose(f);

    Date = makeDateNumber(Points[0].date, Points[0].time);

    return 0;
}

double ToMatrix::value(float lat, float lon)
{
    double val = MISSING_DATA;
    double dist, coef;
    double sigma = 0;

    for (int i = 0; i < NbPoints; i++) {
        if ((Points[i].latitude - lat) > Tolerance)
            break;
        if ((lat - Points[i].latitude) > Tolerance)
            continue;

        // if ((Points[i].longitude - lon) > Tolerance ) continue;
        // if ((lon - Points[i].longitude) > Tolerance ) continue;
        if (longDistance(Points[i].longitude, lon) > Tolerance)
            continue;

        float x = lon - Points[i].longitude;
        float y = lat - Points[i].latitude;
        // Here we have found a point inside the Interval;
        dist = (x * x) + (y * y);
        if (dist == 0)
            return Points[i].value;  // Here the point is on the Grid
        dist = 1 / dist;
        sigma += dist;
        coef = dist * Points[i].value;
        if (val == MISSING_DATA)
            val = coef;
        else
            val += coef;
    }

    if (sigma)
        val = val / sigma;
    return val;
}

float ToMatrix::longDistance(float x1, float x2)
{
    // calculate two ways, in case given values are
    // on the different sides of discontinuity line!

    float min1 = long180(x1) - long180(x2);
    if (min1 < 0.0)
        min1 = -min1;

    float min2 = long360(x1) - long360(x2);
    if (min2 < 0.0)
        min2 = -min2;

    return min1 < min2 ? min1 : min2;
}

void ToMatrix::sortPoints()
{
    float lonmin = 99999;
    float latmin = 99999;
    int min = 99999;
    geopt tmp;


    for (int s = 0; s < NbPoints; s++) {
        lonmin = 99999;
        latmin = 99999;
        min = 99999;
        for (int i = s; i < NbPoints; i++) {
            if (Points[i].latitude < latmin) {
                lonmin = Points[i].longitude;
                latmin = Points[i].latitude;
                min = i;
            }
            else if (Points[i].latitude == latmin) {
                if (Points[i].longitude < lonmin) {
                    lonmin = Points[i].longitude;
                    latmin = Points[i].latitude;
                    min = i;
                }
            }
        }
        if (min != 99999) {
            tmp = Points[s];
            Points[s] = Points[min];
            Points[min] = tmp;
        }
    }
}

void ToMatrix::estimate()
{
    int i, j;
    float lat, lon;

    if ((East - West) == 360)
        East -= GridLon;
    NbLon = ((East - West) / GridLon) + 1;
    NbLat = ((North - South) / GridLat) + 1;

    Matrix = new double[NbLon * NbLat];

    lat = North;
    for (j = 0; j < NbLat; j++) {
        lon = West;
        for (i = 0; i < NbLon; i++) {
            Matrix[i + (j * NbLon)] = value(lat, lon);
            lon += GridLon;
        }
        lat -= GridLat;
    }
}

int ToMatrix::save(const char* path)
{
    FILE* f = fopen(path, "w");
    if (f == nullptr)
        return 1;

    fprintf(f, "#LLMATRIX\n");
    fprintf(f, "GRID = %g/%g\n", GridLat, GridLon);
    fprintf(f, "NORTH = %g\n", North);
    fprintf(f, "SOUTH = %g\n", South);
    fprintf(f, "EAST = %g\n", East);
    fprintf(f, "WEST = %g\n", West);
    fprintf(f, "NLAT = %d\n", NbLat);
    fprintf(f, "NLON = %d\n", NbLon);
    fprintf(f, "DATE = %fl\n", Date);
    fprintf(f, "MISSING = '%g'\n", MISSING_DATA);
    fprintf(f, "#DATA\n");

    // int last;

    for (int j = 0; j < NbLat; j++) {
        for (int i = 0; i < NbLon; i++) {
            // last = i + ( j*NbLon);
            fprintf(f, "%g\n", Matrix[i + (j * NbLon)]);
        }
    }
    fclose(f);
    return 0;
}


void GeoToMatrix::serve(MvRequest& in, MvRequest& out)
{
    std::cout << "GeoToMatrix::serve() in" << std::endl;
    in.print();

    ToMatrix matrix(in);
    matrix.sortPoints();
    matrix.estimate();

    char* path = marstmp();
    if (matrix.save(path)) {
        setError(1, "GeoToMatrix-> Can not write to file %s", path);
        return;
    }


    MvRequest llmatrix("LLMATRIX");
    llmatrix("PATH") = path;
    llmatrix("TEMPORARY") = 0;

    out = llmatrix;

    std::cout << "GeoToMatrix::serve() out" << std::endl;
    out.print();
}

int main(int argc, char** argv)
{
    MvApplication theApp(argc, argv);
    GeoToMatrix tool;
    theApp.run();
}
