/***************************** 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 <stdio.h>

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

#include <gdbm.h>

#include <fcntl.h>
#include <math.h>
#include <ctype.h>
#include <Metview.h>
#include "station.h"
#include "utils.h"
#include <algorithm>

#define MAX_LIST 150

class StationService : public MvService
{
    int count;
    void AddToResult(MvRequest& out, datum& key);
    void AddToResult(MvRequest& out, station& st);
    bool serve_WMO(MvRequest&, MvRequest&);
    bool serve_EPS(MvRequest&, MvRequest&);
    bool serve_location(MvRequest&, MvRequest&);

public:
    StationService();
    void serve(MvRequest&, MvRequest&);

private:
    bool bFailError_;
    char mode;
    position pos;
    station st;
    int ident;
    double north, west, south, east;
    double point_lat, point_lon, threshold;
};

StationService::StationService() :
    MvService("STATIONS"),
    bFailError_(true),
    mode(0),
    north(90.0),
    west(-180.0),
    south(-90.0),
    east(180.0)
{
}

void StationService::AddToResult(MvRequest& out, datum& key)
{
    /* send the request back */
    datum content;
    station st;

    content = gdbm_fetch(db, key);
    std::memcpy(&st, content.dptr, std::min(static_cast<std::size_t>(content.dsize), static_cast<std::size_t>(sizeof(st))));

    AddToResult(out, st);
}

void StationService::AddToResult(MvRequest& out, station& st)
{
    printf("%05d %ld %ld %d %s\n", st.ident, st.pos.lat, st.pos.lon,
           st.pos.station_height, st.name);

    if (count < MAX_LIST) {
        MvRequest y("STATION");

        y("NAME")      = nice_name(st.name);
        y("IDENT")     = st.ident;
        y("LATITUDE")  = st.pos.lat / 100.0;
        y("LONGITUDE") = st.pos.lon / 100.0;
        y("HEIGHT")    = st.pos.station_height;

        out = out + y;
    }
    count++;
}

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

    const char cIDENT = 'I';

    const char* cfail = (const char*)in("FAIL_ON_ERROR");
    if (cfail && strcmp(cfail, "NO") == 0)
        bFailError_ = false;

    const char* p1 = in("STATION_TYPE");
    const char* p2 = in("SEARCH_STATIONS_DATABASE");

    if ((p1 && *p1 == 'L') || (p2 && *p2 == 'N')) {
        serve_location(in, out);
    }
    else {
        p1 = in("SEARCH_KEY");
        if (!p1)
            p1 = &cIDENT;

        switch (*p1) {
            case 'N':
                mode = 'n';
                break;

            case 'I':
                mode  = 'i';
                ident = in("IDENT");
                break;

            case 'W':
                mode  = 'i';
                ident = in("WMO_BLOCK");
                break;

            case 'P':
                mode      = 'l';
                threshold = in("THRESHOLD");
                north     = in("POSITION", 0);
                north += threshold;
                west = in("POSITION", 1);
                west -= threshold;
                south = in("POSITION", 0);
                south -= threshold;
                east = in("POSITION", 1);
                east += threshold;
                break;

            case 'A':
                mode  = 'l';
                north = in("AREA", 0);
                west  = in("AREA", 1);
                south = in("AREA", 2);
                east  = in("AREA", 3);
                if (north < south) {
                    double tmp = north;
                    north      = south;
                    south      = tmp;
                }
                break;
        }

        if (p2 && *p2 == 'E') {
            if (!serve_EPS(in, out))
                return;
        }
        else {
            if (!serve_WMO(in, out))
                return;
        }
    }

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

bool StationService::serve_WMO(MvRequest& in, MvRequest& out)
{
    datum key, k;
    const char* name = nullptr;

    if (open_database(mode)) {
        setError(1, "Station-> Cannot open database %s", getenv("METVIEW_STATIONS"));
        return false;
    }

    count = 0;

    // Look for perfect matches

    switch (mode) {
        case 'n':

            if (in.countValues("NAME") == 1) {
                name = in("NAME");
                strcpy(st.name, upcase(name));
                k.dptr  = (char*)&st.name;
                k.dsize = strlen(st.name) + 1;

                key = gdbm_fetch(db, k);

                if (key.dptr != nullptr) {
                    AddToResult(out, k);
                    gdbm_close(db);
                    return true;
                }
            }
            break;

        case 'i':
            if (in.countValues("IDENT") == 1)
                if (ident >= 01000) {
                    st.ident = ident;
                    k.dptr   = (char*)&st.ident;
                    k.dsize  = sizeof(st.ident);

                    key = gdbm_fetch(db, k);

                    if (key.dptr != nullptr) {
                        AddToResult(out, k);
                        gdbm_close(db);
                        return true;
                    }
                }
            break;

        default:
            break;
    }

    // If no match, loop

    for (key = gdbm_firstkey(db); key.dptr != nullptr; key = gdbm_nextkey(db, key)) {
        int m, ok = 0;
        int n = 0;

        switch (mode) { /* open database related to request */

            case 'n':
                while ((name = in("NAME", n++)) != nullptr)
                    if (same(name, (const char*)key.dptr))
                        ok = 1;
                break;

            case 'i':
                std::memcpy(&m, key.dptr, sizeof(m));
                if (ident < 100)
                    ok = (m / 1000 == ident);
                else
                    ok = (ident == m);
                break;

            case 'l':
                std::memcpy(&pos, key.dptr, sizeof(pos));
                point_lat = ((double)pos.lat) / 100.0;
                point_lon = ((double)pos.lon) / 100.0;
                ok        = inbox(point_lat, point_lon, north, west, south, east);
                break;
        }

        if (ok)
            AddToResult(out, key);
    }

    if (count == 0) {
        if (bFailError_)
            setError(1, "Station-> No matching WMO station found");
        else
            setError(0, "Station-> No matching WMO station found");

        return false;
    }

    if (count > MAX_LIST)
        sendProgress("Station-> Too many stations, only %d out of %d were returned", MAX_LIST, count);

    gdbm_close(db);
    return true;
}

bool StationService::serve_EPS(MvRequest& in, MvRequest& out)
{
    //serve_WMO( in, out ); //-- not yet implementd, use WMO!!!!
    const char* name;
    std::ifstream* EPS_file = nullptr;
    station st;

    if (open_EPS_stationfile(EPS_file)) {
        while (read_one_station(EPS_file, st)) {
            int n  = 0;
            int ok = 0;

            switch (mode) {
                case 'n':
                    while ((name = in("NAME", n++)) != nullptr)
                        if (same(name, st.name))
                            ok = 1;
                    break;

                case 'i':
                    if (ident < 100)
                        ok = (st.ident / 1000 == ident);
                    else
                        ok = (ident == st.ident);
                    break;

                case 'l':
                    point_lat = ((double)st.pos.lat) / 100.0;
                    point_lon = ((double)st.pos.lon) / 100.0;
                    ok        = inbox(point_lat, point_lon, north, west, south, east);
                    break;
            }

            if (ok)
                AddToResult(out, st);
        }
    }

    delete EPS_file;

    return true;
}

bool StationService::serve_location(MvRequest& in, MvRequest& out)
{
    // Don't look in database

    MvRequest y("STATION");

    y("NAME")      = in("NAME");
    y("LATITUDE")  = in("POSITION", 0);
    y("LONGITUDE") = in("POSITION", 1);
    y("THRESHOLD") = in("THRESHOLD");

    if ((const char*)in("HEIGHT"))  //-- pass HEIGHT if HEIGHT set
    {
        // specifically check whether it is exactly '0', and don't add it if it is
        if (strcmp((const char*)in("HEIGHT"), "0")) {
            y("HEIGHT") = in("HEIGHT");
        }

        /*
    int h = (int)in("HEIGHT");
    if( h != 0 )
       y("HEIGHT") = in("HEIGHT");
   */
    }

    out = y;

    return true;
}


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