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

 Copyright 2016 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 "BufrFilterDef.h"

#include "MvMiscellaneous.h"
#include "Tokenizer.h"

#include <iostream>
#if 0
#include <QDateTime>
#include <QString>
#include <QStringList>
#endif


void BufrFilterDef::clear()
{
    vals_.clear();
}

void BufrFilterDef::add(const std::string& key, const std::string& value)
{
    vals_[key] = value;
}

const std::string& BufrFilterDef::value(const std::string& key) const
{
    auto it = vals_.find(key);
    if (it != vals_.end())
        return it->second;

    static std::string emptyStr;
    return emptyStr;
}


#if 0

bool BufrFilterDef::messageFilter(std::string& res,std::string& err) const
{
    res.clear();

    std::string q;

    //Standard header part

    //index
    if(buildIndexQuery(q,err) == false)
        return false;

    //edition
    if(buildEditionQuery(q,err) == false)
        return false;

    //type
    if(buildTypeQuery(q,err) == false)
        return false;

    if(!q.empty())
    {
        q="if(" + q + ") {BLOCK}";
    }

    //Local header  +  Data part
    bool doUnpack=false;
    bool useHeaderOnly=true;

    //ident
    if(buildIdentQuery(q,err,useHeaderOnly))
    {
        if(!useHeaderOnly) doUnpack=true;
    }
    else
        return false;

    //date and time
    if(buildDateQuery(q,err,useHeaderOnly))
    {
        if(!useHeaderOnly) doUnpack=true;
    }
    else
        return false;

    //area
    if(buildAreaQuery(q,err,useHeaderOnly))
    {
        if(!useHeaderOnly) doUnpack=true;
    }
    else
        return false;

    if(doUnpack)
    {
        res="set unpack=1;";
    }

    if(!q.empty())
    {
        replaceBlock(q,"write;");
        res+=q;
    }

    std::cout << "res: " <<res << std::endl;

    return true;
}

void BufrFilterDef::buildQueryWithId(std::string& q,const std::string& id,const std::string& key) const
{
    std::string val=value(id);
    if(!val.empty() && val != "ANY")
    {
        if(!q.empty()) q+=" and";
        q+=" " + key + "==" + val;
    }
}

void BufrFilterDef::buildQueryWithValue(std::string& q,const std::string& key,const std::string& val) const
{
    if(!val.empty() && val != "ANY")
    {
        if(!q.empty()) q+=" and";
        q+=" " + key + "==" + val;
    }
}

void BufrFilterDef::addKeyBlock(std::string& q,const std::string& key,
                                    const std::string& value) const
{
    std::string b="if(defined(" + key + ")) { if(" + key  + " == " + value + ") {BLOCK} }";
    replaceBlock(q,b);
}

bool BufrFilterDef::buildIntQuery(std::string& q,const std::string& key,
                                  const std::string& keyValue,
                                  const std::string& param,std::string& err,
                                  int minVal) const
{
    if(keyValue.empty() || keyValue == "ANY")
        return true;

    int idx=metview::fromString<int>(keyValue);
    if(idx < minVal)
    {
        err="Parameter <b>" + param + "</b>:<br>value is invalid: </b>" + keyValue + "</b>";
        return false;
    }
    buildQueryWithValue(q,keyValue,key);
    return true;
}

bool BufrFilterDef::buildIndexQuery(std::string& q,std::string& err) const
{
    if(!buildIntQuery(q,"count",value("index"),"Message index",err,1))
        return false;

    return true;
}

bool BufrFilterDef::buildEditionQuery(std::string& q,std::string& err) const
{
    if(!buildIntQuery(q,"edition",value("edition"),"Edition",err,1))
        return false;

    if(!buildIntQuery(q,"bufrHeaderCentre",value("centre"),"Centre",err,1))
        return false;

    if(!buildIntQuery(q,"bufrHeaderSubCentre",value("subCentre"),"Subcentre",err,1))
        return false;

    if(!buildIntQuery(q,"masterTablesVersionNumber",value("masterTable"),"Master table",err,1))
        return false;

    if(!buildIntQuery(q,"localTablesVersionNumber",value("localTable"),"Local table",err,1))
        return false;

    return true;
}

bool BufrFilterDef::buildTypeQuery(std::string& q,std::string& err) const
{
    if(!buildIntQuery(q,"dataCategory",value("type"),"Type",err,1))
        return false;

    if(!buildIntQuery(q,"dataSubCategory",value("subType"),"Subtype",err,1))
        return false;

    return true;
}

bool BufrFilterDef::buildIdentQuery(std::string& q,std::string& err,bool& headerOnly) const
{
    headerOnly=true;
    std::string keyValue=value("ident");

    if(keyValue.empty() || keyValue == "ANY")
        return true;

    addKeyBlock(q,"ident",keyValue);

    return true;

#if 0
    std::string keyName=value("identKey");
    std::string keyValue=value("identValue");

    if(keyName.empty() || keyValue.empty())
        return;

    if(keyName == "ident")
    {
        addKeyBlock(q,keyName,keyValue);
    }

    else if(keyName == "wmo_station_id")
    {
        std::string b="if(defined(blockNumber) and defined(stationNumber)) { \
           transient statid=1000*blockNumber+stationNumber;\
           if(statid == " + keyValue + ") {BLOCK} }";
        replaceBlock(q,b);
        headerOnly=false;
    }
    else
    {
        addKeyBlock(q,keyName,keyValue);
        headerOnly=false;
    }
#endif
}

bool BufrFilterDef::buildAreaQuery(std::string& q,std::string& err,bool& headerOnly) const
{
    headerOnly=true;
    std::string keyValue=value("area");

    if(keyValue.empty() || keyValue == "-180,90,180,-90")
       return true;

    std::vector<std::string> area;
    Tokenizer t(",");
    t(keyValue,area);

    if(area.size() == 4)
    {
        float w=metview::fromString<float>(area[0]);
        float n=metview::fromString<float>(area[1]);
        float e=metview::fromString<float>(area[2]);
        float s=metview::fromString<float>(area[3]);

        std::string err;
        if(checkLon(w,e,err) == false)
        {
            err="Parameter <b>Area</b>:<br>" + err;
            return false;
        }

        if(checkLat(n,s,err) == false)
        {
            err="Parameter <b>Area</b>:<br>" + err;
            return false;
        }

        std::string b="if(defined(localLatitude) and defined(localLongitude)) { \
           if(localLongitude >= " + area[0] + " and localLongitude <= " + area[2] +
           " and localLatitude > " + area[3] + " and localLatitude <= " + area[1] + ") {BLOCK} }";
        replaceBlock(q,b);
    }

    return true;
}


bool BufrFilterDef::buildDateQuery(std::string& q,std::string& err,bool& headerOnly) const
{
    headerOnly=true;
    std::string mode=value("dateMode");
    QString dateFormat("yyyyMMdd");
    QString inTimeFormat("hhmmss");
    QString outTimeFormat("hhmmss");

    //Time window
    if(mode == "window")
    {
        std::string hour, minute, second;       
        std::string dateVal=value("date");
        if(dateVal == "ANY") dateVal.clear();
        if(parseDate(dateVal,err) == false)
        {            
            err="Paramater <b>Date</b>:<br>" + err;
            return false;
        }

        std::string timeVal=value("time");
        if(timeVal == "ANY") timeVal.clear();
        if(parseTime(timeVal,hour,minute,second,err) == false)
        {            
            err="Paramater <b>Time</b>:<br>" + err;
            return false;
        }

        std::string winVal=value("window");
        int winSec=-1;
        if(parseTimeWindow(winVal,winSec,err) == false)
        {            
            err="Paramater <b>Window</b>:<br>" + err;
            return false;
        }

        bool hasDate=(!dateVal.empty());
        bool hasTime=(!timeVal.empty());

        //A valid window
        if(winSec > 0)
        {
            //Has date and time
            if(hasDate && hasTime)
            {
                QDate d=QDate::fromString(QString::fromStdString(dateVal),
                                          dateFormat);

                QTime t=QTime::fromString(QString::fromStdString(timeVal),inTimeFormat);
                QDateTime mid(d,t);
                QDateTime start=mid.addSecs(-winSec);
                QDateTime end=mid.addSecs(winSec);

                std::string startDate=start.toString(dateFormat).toStdString();
                std::string endDate=end.toString(dateFormat).toStdString();
                std::string startTime=start.toString(outTimeFormat).toStdString();
                std::string endTime=end.toString(outTimeFormat).toStdString();

                std::string b=periodQuery(startDate,startTime,endDate,endTime);
                replaceBlock(q,b);

            }

            //Only has time
            else if (!hasDate && hasTime)
            {
                  QTime t=QTime::fromString(QString::fromStdString(timeVal),inTimeFormat);
                  QTime start=t.addSecs(-winSec);
                  QTime end=t.addSecs(winSec);
                  std::string startTime=start.toString(outTimeFormat).toStdString();
                  std::string endTime=end.toString(outTimeFormat).toStdString();

                  std::string b=periodTimeQuery(startTime,endTime);
                  replaceBlock(q,b);
            }
            else
            {
                err="Either parameter <b>Date</b> or <b>Time</b> not speficied properly.";
                return false;
            }
        } //end window

        //No window just exact date and time
        else
        {
            //Has date and time
            if(hasDate && hasTime)
            {
                std::string b=periodQuery(dateVal,timeVal,dateVal,timeVal);
                replaceBlock(q,b);
            }
            //only date, no time
            else if(hasDate && !hasTime)
            {
                std::string b=periodDateQuery(dateVal,dateVal);
                replaceBlock(q,b);
            }
            //only time, no date
            else if(!hasDate && hasTime)
            {
                std::string b=periodTimeQuery(timeVal,timeVal);
                replaceBlock(q,b);
            }              
        }

    } //end window mode

    //Period mode
    else
    {
        std::string hour1, minute1, second1;
        std::string dateVal1=value("date1");
        if(dateVal1 == "ANY") dateVal1.clear();
        if(parseDate(dateVal1,err) == false)
        {
            err="Paramater <b>Date1</b>:<br>" + err;
            return false;
        }

        std::string timeVal1=value("time1");
        if(timeVal1 == "ANY") timeVal1.clear();
        if(parseTime(timeVal1,hour1,minute1,second1,err) == false)
        {
            err="Paramater <b>Time1</b>:<br>" + err;
            return false;
        }

        std::string hour2, minute2, second2;
        std::string dateVal2=value("date2");
        if(dateVal2 == "ANY") dateVal2.clear();
        if(parseDate(dateVal2,err) == false)
        {
            err="Paramater <b>Date1</b>:<br>" + err;
            return false;
        }

        std::string timeVal2=value("time2");
        if(timeVal2 == "ANY") timeVal2.clear();
        if(parseTime(timeVal2,hour2,minute2,second2,err) == false)
        {
            err="Paramater <b>Time2</b>:<br>" + err;
            return false;
        }

        bool hasDate1=(!dateVal1.empty());
        bool hasTime1=(!timeVal1.empty());

        bool hasDate2=(!dateVal2.empty());
        bool hasTime2=(!timeVal2.empty());

        //Has date and time
        if(hasDate1 && hasDate2 && hasTime1 && hasTime2)
        {
            std::string b=periodQuery(dateVal1,timeVal1,dateVal2,timeVal2);
            replaceBlock(q,b);
        }
        //only dates, no times
        else if(hasDate1 && hasDate2 && !hasTime1 && !hasTime2)
        {
            std::string b=periodDateQuery(dateVal1,dateVal2);
            replaceBlock(q,b);
        }
        //only times, no dates
        else if(!hasDate1 && !hasDate2 && hasTime1 && hasTime2)
        {
            std::string b=periodTimeQuery(timeVal1,timeVal2);
            replaceBlock(q,b);
        }
        else if(!hasDate1 && !hasDate2 && !hasTime1 && !hasTime2)
        {
            return true;
        }
        else
        {
             err="Period is not speficied properly.";
             return false;
        }
    }

    return true;
}

bool BufrFilterDef::parseDate(std::string& val,std::string& err) const
{
    if(val.empty())
        return true;

    int y,m,d;
    if(val.size()==8)
    {
        y=metview::fromString<int>(val.substr(0,4));
        m=metview::fromString<int>(val.substr(4,2));
        d=metview::fromString<int>(val.substr(6,2));

        if(y < 1000 || y > 9999)
        {
            err="Invalid year specified: <b>" + metview::toString(y) + "</b>";
            return false;
        }
        if(m < 1 || m > 12)
        {
            err="Invalid month specified";
            return false;
        }
        if(d < 1 || d > 31)
        {
            err="Invalid day specified";
            return false;
        }
        return true;
    }
    else
    {
        err="Invalid date: " + toBold(val);
        return false;
    }

    return false;
}

bool BufrFilterDef::parseTime(std::string& val,std::string& hour,std::string& minute,
                              std::string& second,std::string& err) const
{
    if(val.empty())
        return true;

    int h=-1;
    int m=-1;
    int s=-1;

    if(val.size() == 6 )
    {
        hour=val.substr(0,2);
        minute=val.substr(2,2);
        second=val.substr(4,2);
        h=metview::fromString<int>(hour);
        m=metview::fromString<int>(minute);
        s=metview::fromString<int>(second);

        if(!checkHour(h,err)) return false;
        if(!checkMinute(m,err)) return false;
        if(!checkSecond(s,err)) return false;
        return true;
    }
    else if(val.size() == 4 )
    {
        hour=val.substr(0,2);
        minute=val.substr(2,2);
        h=metview::fromString<int>(hour);
        m=metview::fromString<int>(minute);

        if(!checkHour(h,err)) return false;
        if(!checkMinute(m,err)) return false;
        val=val + "00";
        return true;
    }
    else if(val.size() == 2 )
    {
        hour=val.substr(0,2);
        h=metview::fromString<int>(hour);
        if(!checkHour(h,err)) return false;
        val=val + "0000";
        return true;
    }
    else if(val.size() == 1 )
    {
        hour=val.substr(0,2);
        h=metview::fromString<int>(hour);
        if(!checkHour(h,err)) return false;
        val="0" + val + "0000";
        return true;
    }
    else if(val.empty())
        return true;

    else
    {
        err="Invalid time: " + toBold(val);
        return false;
    }

    return false;
}

bool BufrFilterDef::checkHour(int h,std::string& err) const
{
    if(h < 0 || h > 24)
    {
        err="Invalid hour: " + toBold(h);
        return false;
    }
    return true;
}

bool BufrFilterDef::checkMinute(int m,std::string& err) const
{
    if(m <0  || m > 59)
    {
        err="Invalid minute: " + toBold(m);
        return false;
    }
    return true;
}

bool BufrFilterDef::checkSecond(int s,std::string& err) const
{
    if(s <0  || s > 59)
    {
        err="Invalid second: " + toBold(s);
        return false;
    }
    return true;
}

bool BufrFilterDef::parseTimeWindow(std::string& winVal,int& winSec,std::string& err) const
{
    winSec=0;
    if(winVal.empty())
        return true;

    winSec=metview::fromString<int>(winVal);
    if(winSec < 0)
    {
        err="Invalid time window: " + toBold(winVal);
        return false;
    }
    winSec*=60;
    return true;
}

bool BufrFilterDef::checkLon(float lon1,float lon2,std::string& err) const
{
    if(lon1 < -180)
    {
        err="Invalid longitude: " + toBold(lon1);
        return false;
    }
    if(lon2 > 180)
    {
        err="Invalid longitude: " + toBold(lon2);
        return false;
    }
    return true;
}

bool BufrFilterDef::checkLat(float lat1,float lat2,std::string& err) const
{
    if(lat1 > 90)
    {
        err="Invalid latitude: " + toBold(lat1);
        return false;
    }
    if(lat2 < -90)
    {
        err="Invalid latitude: " + toBold(lat2);
        return false;
    }
    return true;
}

void BufrFilterDef::replaceBlock(std::string& q,const std::string& block) const
{
    QString qStr=QString::fromStdString(q);
    QString bStr=QString::fromStdString(block);
    if(qStr.isEmpty())
    {
        q=block;
    }
    else
    {
        qStr.replace("BLOCK",bStr);
        q=qStr.toStdString();
    }
}

std::string BufrFilterDef::periodQuery(const std::string& startDate,const std::string& startTime,
                                       const std::string& endDate,const std::string& endTime) const
{

    std::string b;

    if(startDate == endDate)
    {
        b=
"if(defined(typicalDate)) { \
if( typicalDate == " + startDate +  " ) {\
if(defined(typicalHour) and defined(typicalMinute)) {\
transient LONGTIME=typicalHour*10000+typicalMinute*100; \
if(defined(typicalSecond)) {\
    set LONGTIME=LONGTIME+typicalSecond;\
}\
if(LONGTIME >= " + startTime + " and LONGTIME <= " + endTime + ") { BLOCK }}}}";
    }
    else
    {
        b=
"if(defined(typicalDate)) { \
if( typicalDate >= " + startDate + " and typicalDate <= " + endDate + " ) {\
if(defined(typicalHour) and defined(typicalMinute)) {\
transient LONGTIME=typicalHour*10000+typicalMinute*100; \
if(defined(typicalSecond)) {\
set LONGTIME=LONGTIME+typicalSecond;\
}\
if((typicalDate > " + startDate + "  and typicalDate < " + endDate + ") or \
(typicalDate == " + startDate +  " and LONGTIME >= " + startTime + ") or \
(typicalDate == " + endDate + " and LONGTIME <= " + endTime + ")) { BLOCK }}}}";
    }
    return b;
}

std::string BufrFilterDef::periodTimeQuery(const std::string& startTime,const std::string& endTime) const
{
    std::string b;

    if(startTime == endTime)
    {
        b=
    "if(defined(typicalHour) and defined(typicalMinute)) {\
    transient LONGTIME=typicalHour*10000+typicalMinute*100; \
    if(defined(typicalSecond)) {\
        set LONGTIME=LONGTIME+typicalSecond;\
    }\
    if(LONGTIME == " + startTime + ") { BLOCK }}";
    }
    else
    {
        b=
"if(defined(typicalHour) and defined(typicalMinute)) {\
transient LONGTIME=typicalHour*10000+typicalMinute*100; \
if(defined(typicalSecond)) {\
set LONGTIME=LONGTIME+typicalSecond;\
}\
if(LONGTIME >= " + startTime + " and LONGTIME <= " + endTime + ") { BLOCK }}";
       }
    return b;
}


std::string BufrFilterDef::periodDateQuery(const std::string& startDate,const std::string& endDate) const
{
    std::string b;

    if(startDate == endDate)
    {
        b=
"if(defined(typicalDate)) { \
if( typicalDate == " + startDate +  " ) { BLOCK }}";
    }
    else
    {
        b=
"if(defined(typicalDate)) { \
if( typicalDate >= " + startDate + " and typicalDate <= " + endDate + " ) { BLOCK }}";
     }

    return b;
}


std::string BufrFilterDef::toBold(int v) const
{
    std::string s;
    s="<b>" + metview::toString(v) + "</b>";
    return s;
}

std::string BufrFilterDef::toBold(float v) const
{
    std::string s;
    s="<b>" + metview::toString(v) + "</b>";
    return s;
}

std::string BufrFilterDef::toBold(const std::string& v) const
{
    std::string s;
    s="<b>" + v + "</b>";
    return s;
}
#endif
