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

// This is an extended version of llmatrix_to_grib function
// available in Mars library.  Within Metview both 'togrib'
// and macro now use this extended version.
//                                             vk/1999-07-19
// Re-written to use GRIB_API                  vk/2009-07-07

#include "LLMatrixToGRIB.h"

#include <fstream>
#include "fstream_mars_fix.h"

#include "Metview.h"
#include "UtilitiesC.h"
#include "grib_api.h"
#include <math.h>

void do_swap(double& s1, double& s2)
{
    double t = s1;
    s1 = s2;
    s2 = t;
}

err LLMatrixToGRIB(const char* matrixFile, const char* gribFileName)
{
    //-- open LatLonMatrix file

    std::ifstream matrixStream(matrixFile, std::ios::in);
    if (!matrixStream) {
        marslog(LOG_EROR | LOG_PERR, "Cannot open %s", matrixFile);
        return -1;
    }

    //-- temporary file to store LatLonMatrix metadata as a Request

    char* requestFile = marstmp();
    FILE* reqFILE = fopen(requestFile, "w");
    if (!reqFILE) {
        marslog(LOG_EROR | LOG_PERR, "Cannot open %s", requestFile);
        return -1;
    }

    //-- extract LatLonMatrix metadata and write metadata into a Request file

    int n = 0;
    char line[1024];
    matrixStream.getline(line, sizeof(line));
    while (matrixStream.gcount() > 0) {
        if (*line != '#') {
            int l = strlen(line) - 1;

            while (l >= 0 && (line[l] == ' ' ||
                              line[l] == '\n' ||
                              line[l] == ',' ||
                              line[l] == '\t'))
                line[l--] = 0;


            if (n == 0) {
                // Look for a '='
                char* p = line;
                int flg = 0;
                while (*p)
                    if (*p++ == '=')
                        flg++;
                // If a '=', add a verb
                if (flg) {
                    fprintf(reqFILE, "LLMATRIX");
                    fprintf(reqFILE, ",%s", line);
                }
                else
                    fprintf(reqFILE, "%s", line);
            }
            else
                fprintf(reqFILE, ",%s", line);
            n++;
        }
        if (strncmp(line, "#DATA", 5) == 0)
            break;

        matrixStream.getline(line, sizeof(line));
    }

    fclose(reqFILE);

    //-- read into an internal Request structure and remove the temporary file

    request* r = read_request_file(requestFile);
    unlink(requestFile);
    print_all_requests(r);

    //-- extract grid related metadata

    const char* p;
    double north = atof((p = get_value(r, "NORTH", 0)) ? p : "0");
    double west = atof((p = get_value(r, "WEST", 0)) ? p : "0");
    int nlat = atol((p = get_value(r, "NLAT", 0)) ? p : "0");
    int nlon = atol((p = get_value(r, "NLON", 0)) ? p : "0");
    double ew = atof((p = get_value(r, "GRID", 0)) ? p : "0");
    double ns = atof((p = get_value(r, "GRID", 1)) ? p : "0");

    if (ns == 0)
        ns = ew;

    if (ew == 0 || ns == 0 || nlat == 0 || nlon == 0) {
        marslog(LOG_EROR, "Invalid ll-matrix");
        return -1;
    }

    p = get_value(r, "HSCAN", 0);  //-- 'WE' (default) or 'EW'
    int hscan = (p && (*p == 'E' || *p == 'e')) ? 128 : 0;

    p = get_value(r, "VSCAN", 0);  //-- 'NS' (default) or 'SN'
    int vscan = (p && (*p == 'S' || *p == 's')) ? 64 : 0;

    p = get_value(r, "SCANDIR", 0);  //-- 'HORIZONTAL' (default) or 'VERTICAL'
    int scandir = (p && (*p == 'V' || *p == 'v')) ? 32 : 0;

    double missing = (p = get_value(r, "MISSING", 0)) ? atof(p) : mars.grib_missing_value;

    //-- extract PARAM, TABLE2, LEVEL, CENTRE, DATE, FCAST

    int param = (p = get_value(r, "PARAM", 0)) ? atol(p) : 255;
    int table = (p = get_value(r, "TABLE2", 0)) ? atol(p) : 128;
    int level = (p = get_value(r, "LEVEL", 0)) ? atol(p) : 0;
    int centre = (p = get_value(r, "CENTRE", 0)) ? atol(p) : localWmoSiteNumber();
    if (param == 0)
        param = 255;

    long msave = expand_flags(0);
    p = get_value(r, "DATE", 0);
    double dseed = p ? atof(p) : 0.0;
    MvDate date(dseed);
    expand_flags(msave);

    int fc_len = (p = get_value(r, "FCAST", 0)) ? atol(p) : 0;


    //-- set correct boarder values

    double first_lat = north;
    double last_lat = north - (nlat - 1) * ns;

    if (vscan > 0)
        do_swap(first_lat, last_lat);

    double first_lon = west;
    double last_lon = west + (nlon - 1) * ew;

    if (hscan > 0)
        do_swap(first_lon, last_lon);

    /* ============================================================ */

    //-- read data values from LatLonMatrix file

    int npoints = nlat * nlon;
    auto* dataValues = new double[npoints];

    n = 0;
    int miss = 0;
    for (int i = 0; i < npoints; ++i) {
        if (matrixStream.eof()) {
            dataValues[i] = mars.grib_missing_value;
            miss++;
        }
        else {
            double myValue = DBL_MAX;
            matrixStream >> myValue;

            if (myValue == DBL_MAX)  //-- check read success
            {
                dataValues[i] = mars.grib_missing_value;
                miss++;
            }
            else {
                dataValues[i] = myValue;
                ++n;

                if (dataValues[i] == missing) {
                    miss++;  //-- adapt to mars
                    dataValues[i] = mars.grib_missing_value;
                }
            }

            if (matrixStream.peek() == ',')
                matrixStream.ignore(1, ',');  //-- skip possible comma separators
        }
    }
    matrixStream.close();

    if (n != npoints) {
        delete[] dataValues;
        marslog(LOG_EROR, "LLMatrixToGRIB: file %s, read %d values out of %d expected",
                matrixFile, n, npoints);
        return -1;
    }

    /* ============================================================ */

    //-- create a new grib handle from a ecCodes sample file

    const char* sample = "regular_ll_sfc_grib1";

    grib_handle* h = grib_handle_new_from_samples(0, sample);
    if (!h) {
        delete[] dataValues;
        marslog(LOG_EROR, "Failed to create handle from sample %s[.tmpl]", sample);
        return -1;
    }

    //-- start modifying the GRIB metadata, according to LatLonMatrix metadata

    int stat = 0;
    stat += grib_set_long(h, "table2Version", table);
    stat += grib_set_long(h, "centre", centre);
    stat += grib_set_long(h, "gridDefinition", 255);
    stat += grib_set_long(h, "section1Flags", miss ? 192 : 128);
    stat += grib_set_long(h, "indicatorOfParameter", param);

    if (stat > 0) {
        delete[] dataValues;
        marslog(LOG_EROR, "Errors calling 'ecCodes' (1)!");
        return -1;
    }


    //-- UKMO uses 'generating process' octet as part of their STASH code;
    //-- use env.variable MV_GENERATING_PROCESS to set a custom value
    const char* generProcAscii = getenv("MV_GENERATING_PROCESS");
    if (generProcAscii)
        stat += grib_set_long(h, "generatingProcessIdentifier", atoi(generProcAscii));
    else
        stat += grib_set_long(h, "generatingProcessIdentifier", mars.computeflg);

    //-- value zero for 'generating process' causes problems!
    long generProc = 0;
    stat += grib_get_long(h, "generatingProcessIdentifier", &generProc);
    if (generProc == 0)
        stat += grib_set_long(h, "generatingProcessIdentifier", 255);


    stat += grib_set_long(h, "indicatorOfUnitOfTimeRange", 1);
    if (fc_len > 0) {
        stat += grib_set_long(h, "periodOfTime", fc_len);
        stat += grib_set_long(h, "marsType", 9);  //-- forecast
    }

    if (level == 0) {
        stat += grib_set_long(h, "levtype", GRIB_SURFACE);
    }
    else {
        stat += grib_set_long(h, "levtype", GRIB_PRESSURE_LEVEL);
        stat += grib_set_long(h, "level", level);
    }

#if 0
//??????
	if( centre == 98 ) //-- ??????????????????? vk/20090707
	{
		locsec1.ecmwf_local_id  = GRIB_ENSEMBLE_FORECAST;
		locsec1.mars_class      = GRIB_CLASS_OD;
		locsec1.mars_type       = 2;          /* AN */
		locsec1.mars_stream     = 1025;       /* DA */
		locsec1.mars_expver     = 0x30303031; /* 01 */

		ksec1.local_use         = 1;
		ksec1.local             = locsec1;
	}

	ksec2.data_rep        = GRIB_LAT_LONG;
#endif
    stat += grib_set_long(h, "date", date.YyyyMmDd());
    stat += grib_set_long(h, "time", date.hhmmss() / 100);


    stat += grib_set_long(h, "Ni", nlon);
    stat += grib_set_long(h, "Nj", nlat);

    stat += grib_set_double(h, "latitudeOfFirstGridPointInDegrees", first_lat);
    stat += grib_set_double(h, "longitudeOfFirstGridPointInDegrees", first_lon);
    stat += grib_set_double(h, "latitudeOfLastGridPointInDegrees", last_lat);
    stat += grib_set_double(h, "longitudeOfLastGridPointInDegrees", last_lon);
    stat += grib_set_long(h, "scanningMode", hscan + vscan + scandir);

    if (stat > 0) {
        delete[] dataValues;
        marslog(LOG_EROR, "Errors calling 'ecCodes' (2)!");
        return -1;
    }

    int ewi = ew * GRIB_FACTOR;
    int nsi = ns * GRIB_FACTOR;

    //----------------------------------------------------------//
    //-- GRIB1 precision is millidegrees: 0.001 degrees only! --//
    //-- If grid interval requires more precision then do not --//
    //-- store grid intervals (let them to be computed).      --//
    //----------------------------------------------------------//
    bool not_enough_precision =
        (fabs(ew - (double)ewi / GRIB_FACTOR) > 0.00005) ||
        (fabs(ns - (double)nsi / GRIB_FACTOR) > 0.00005);

    stat += grib_set_long(h, "ijDirectionIncrementGiven", not_enough_precision ? 0 : 1);
    stat += grib_set_double(h, "iDirectionIncrementInDegrees", not_enough_precision ? 0 : ew);
    stat += grib_set_double(h, "jDirectionIncrementInDegrees", not_enough_precision ? 0 : ns);

    if (stat > 0) {
        delete[] dataValues;
        marslog(LOG_EROR, "Errors calling 'ecCodes' (3)!");
        return -1;
    }


    //  Missing value

    stat += grib_set_long(h, "bitmapPresent", 1);
    stat += grib_set_double(h, "missingValue", mars.grib_missing_value);

    if (stat > 0) {
        delete[] dataValues;
        marslog(LOG_EROR, "Errors calling 'ecCodes' (4)!");
        return -1;
    }

    //--  Data

    grib_set_long(h, "numberOfPoints", npoints);

    int nbits = 24;  //-- currently hard coded
    grib_set_long(h, "bitsPerValue", nbits);

    printf("nbits   = %d\n", nbits);
    printf("missing = %g\n", missing);

    //-- set new grid point values
    grib_set_double_array(h, "values", dataValues, npoints);

    //-- get the GRIB1 message into 'buffer'
    size_t buflen = 0;
    const void* buffer = nullptr;
    stat = grib_get_message(h, &buffer, &buflen);

    if (stat > 0) {
        delete[] dataValues;
        marslog(LOG_EROR, "Errors calling 'ecCodes' (5)!");
        return -1;
    }

    //-- write the GRIB1 message from 'buffer' to the file

    FILE* gribFile = fopen(gribFileName, "w");
    if (!gribFile) {
        delete[] dataValues;
        marslog(LOG_EROR | LOG_PERR, "Cannot open %s", gribFileName);
        return -1;
    }

    err e = 0;
    if (fwrite(buffer, 1, buflen, gribFile) != buflen) {
        marslog(LOG_EROR | LOG_PERR, "fwrite(%s) failed", gribFileName);
        e = -1;
    }

    if (fclose(gribFile)) {
        marslog(LOG_EROR | LOG_PERR, "fclose(%s) failed", gribFileName);
        e = -1;
    }

    delete[] dataValues;
    //	delete [] buffer;
    return e;
}
