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

#include <stdlib.h>
#include <unistd.h>  // for AIX to know symlink()
#include <sys/stat.h>

#include <iostream>
#include <list>
#include <set>
#include <string>

#include "MvObsSet.h"

namespace metview
{

const std::string cNoBufrTablesDir("NO_TABLE_DIR");
const std::string cCommonNamePart("_prepbufr.txt");
const std::string cAUX_TABLE_B("prepbufr_table_B.txt");
const std::string cAUX_TABLE_D("prepbufr_table_D.txt");


bool compare_tab_B_elems(const TableB_entry* first, const TableB_entry* second)
{
    return first->descr().compare(second->descr()) < 0;
}

bool compare_tab_D_elems(const TableD_entry* first, const TableD_entry* second)
{
    return first->descr().compare(second->descr()) < 0;
}


//______________________________________________________________________
//
//   MvPrepBufrPrep
//______________________________________________________________________

MvPrepBufrPrep::~MvPrepBufrPrep()
{
    // revertBufrTablesDir(); //-- this would change nothing as BUFR_TABLES
    //-- will not be read anymore

    // std::string remove = "rm -r " + prepBufrTableDir_ + "/*";
    // std::cout << "rm command is: " << remove << std::endl;
    //   int s = system( remove.c_str() );
}

//______________________________________________________________________

bool MvPrepBufrPrep::prepareAll()
{
    bool ok = createPrepBufrTableDir();  //-- new unique subdir to play with table links
    if (!ok)
        return false;

    MvObs firstObs = obsSet_.firstObs();     //-- create links to our tables that are
    ok = createTablesDecodeLinks(firstObs);  //-- needed to decode prepBUFR tables
    if (!ok)
        return false;

    ok = setNewBufrTablesDir();  //-- (re)assign BUFR_TABLES env.var
    if (!ok)
        return false;

    ok = extractPrepBufrTables();  //-- extract PrepBUFR tables using fixed table names
    if (!ok)
        return false;

    ok = createDataDecodeLinks();  //-- 'bufrdc' file names link to fixed name PrepBUFR tables
    if (!ok)
        return false;

    return true;
}

//______________________________________________________________________

bool MvPrepBufrPrep::createPrepBufrTableDir()
{
    //-- store current value (if any)
    const char* oldDir = getenv("BUFR_TABLES");
    origBufrTableDir_ = oldDir ? oldDir : cNoBufrTablesDir;
    std::cout << "MvPrepBufrPrep::prepareTempDir: origBufrTableDir_=" << origBufrTableDir_ << std::endl;

    // char dirTemplate[1024];                   //-- build template for unique subdir name
    // strcpy( dirTemplate, getenv("METVIEW_TMPDIR") );
    // strcat( dirTemplate, "/PrepBufrTablesXXXXXX" );

    // to create a unique temporary directory name without using mkdtemp (which does not
    // work on AIX), we create a temporary file, append '_dir' and create a directory
    // with that name. This should be safe in almost 100% of cases...

    char* p;
    p = tempnam(nullptr, "PrepBUFR");
    std::string tmpfilename(p);
    free(p);
    tmpfilename += "_dir";
    int ok = mkdir(tmpfilename.c_str(), S_IRWXU);

    if (ok != 0) {
#ifdef METVIEW
        marslog(LOG_EROR, "MvPrepBufrPrep::createPrepBufrTableDir: Unable to create directory %s", tmpfilename.c_str());
#endif
        std::cerr << "MvPrepBufrPrep::createPrepBufrTableDir: Unable to create directory "
                  << tmpfilename
                  << std::endl;
        return false;
    }

    prepBufrTableDir_ = tmpfilename;  // mkdtemp(dirTemplate); //-- create a unique subdir for PrepBufr tables
    std::cout << "MvPrepBufrPrep::prepareTempDir: prepBufrTableDir_=" << prepBufrTableDir_ << std::endl;

    return true;
}

//______________________________________________________________________
//  Build links (using file names required by 'bufrdc') to custom table
//  files stored in 'etc', needed to extract PrepBUFR Tables from ObsSet.
//  This is because PrepBUFR table msgs may require non-existing tables.

bool MvPrepBufrPrep::createTablesDecodeLinks(MvObs& firstObs)
{
    std::ostringstream commonPart;  //-- build part of filename common to both files
    int centre = firstObs.originatingCentre();
    int subCentre = firstObs.originatingSubCentre();
    int masterTableVer = firstObs.masterTableVersion();
    int localTableVer = firstObs.localTableVersion();

    commonPart << "00"  //-- discipline: 0 = meteorology
               << std::setw(6) << std::setfill('0') << subCentre
               << std::setw(5) << std::setfill('0') << centre
               << std::setw(3) << std::setfill('0') << masterTableVer
               << std::setw(3) << std::setfill('0') << localTableVer
               << ".TXT"
               << std::ends;

    std::string table_B_name = prepBufrTableDir_ + "/B" + commonPart.str();
    std::string table_D_name = prepBufrTableDir_ + "/D" + commonPart.str();

    //-- links to the real table files in dir 'etc'
    std::string mvShareDir(getenv("METVIEW_DIR_SHARE"));
    std::string etc_auxTable_B = mvShareDir + "/etc/AuxPrepBufrTable_B.txt";
    std::string etc_auxTable_D = mvShareDir + "/etc/AuxPrepBufrTable_D.txt";

    std::string auxTable_B = prepBufrTableDir_ + "/" + cAUX_TABLE_B;
    std::string auxTable_D = prepBufrTableDir_ + "/" + cAUX_TABLE_D;

    //--  1) copy table file from etc/share and make writable (we need to append PrepBUFR entries)
    std::string copyTableB = "cp " + etc_auxTable_B + " " + auxTable_B + "; chmod u+w " + auxTable_B + ";";
    std::cout << copyTableB << std::endl;
    int ok = 1;

    system(copyTableB.c_str());

    //--  2) create 'bufrex' named link to this file
    ok = symlink(auxTable_B.c_str(), table_B_name.c_str());
    if (ok != 0) {
#ifdef METVIEW
        marslog(LOG_EROR, "Unable to copy auxiliary BUFR Table B");
#endif
        std::cerr << "Unable to copy auxiliary BUFR Table B" << std::endl;
        return false;
    }

    //--  1) copy table file from etc/share and make writable (we need to append PrepBUFR entries)
    std::string copyTableD = "cp " + etc_auxTable_D + " " + auxTable_D + "; chmod u+w " + auxTable_D + ";";
    std::cout << copyTableD << std::endl;
    system(copyTableD.c_str());

    //--  2) create 'bufrex' named link to this file
    ok = symlink(auxTable_D.c_str(), table_D_name.c_str());
    if (ok != 0) {
#ifdef METVIEW
        marslog(LOG_EROR, "Unable to create a link into auxiliary BUFR Table D");
#endif
        std::cerr << "Unable to create a link into auxiliary BUFR Table D" << std::endl;
        return false;
    }

    return true;
}
//______________________________________________________________________
//  Assigns BUFR_TABLES to point to the temporary directory which by now
//  should contain links to auxiliary BUFR tables needed to decode the
//  prepBUFR tables, and where the prepBUFR tables will be extracted to.

bool MvPrepBufrPrep::setNewBufrTablesDir()
{
    const int overWrite = 1;
    std::string newEnvVar = prepBufrTableDir_ + "/";

    int ok = setenv("BUFR_TABLES", newEnvVar.c_str(), overWrite);
    if (ok != 0) {
#ifdef METVIEW
        marslog(LOG_EROR, "Unable change to BUFR_TABLES env.variable");
#endif
        std::cerr << "Unable change to BUFR_TABLES env.variable" << std::endl;
        return false;
    }

    std::cout << "MvPrepBufrPrep::setNewBufrTablesDir: BUFR_TABLES new values is "
              << newEnvVar << std::endl;

    return true;
}
//______________________________________________________________________
//  Build file names for temporary PrepBUFR tables and
//  extract Table B and Table D from the current ObsSet.

bool MvPrepBufrPrep::extractPrepBufrTables()
{
    prepBufrTable_B_ = prepBufrTableDir_ + "/" + cAUX_TABLE_B;  //-- build table B filename
    prepBufrTable_D_ = prepBufrTableDir_ + "/" + cAUX_TABLE_D;  //-- build table D filename

    MvTableExtract extract(obsSet_);
    return extract.extract(prepBufrTable_B_, prepBufrTable_D_);
}
//______________________________________________________________________
//  Create links (using 'bufrdc' table file names) to temporary PrepBUFR
//  table files. Make sure that there are links corresponding to all
//  'bufrdc' table file names that are needed to decode any BUFR data
//  message in the current ObsSet.
//  Note that PrepBUFR ObsSet may contain msgs with different table or
//  local table version, i.e. we may need several different link names.

bool MvPrepBufrPrep::createDataDecodeLinks()
{
    std::set<std::string> tables;  //-- keep track of existing table links
    std::set<std::string>::iterator it;

    obsSet_.rewind();
    MvObsSetIterator iter(obsSet_);
    MvObs obs;

    while ((obs = iter()))  //-- search all msgs for the required 'bufrdc' table names
    {
        if (obs.messageType() == 11)  //-- skip if msg contains BUFR tables
            continue;
        //-- get values used in building table names
        int centre = obs.originatingCentre();
        int subCentre = obs.originatingSubCentre();
        int masterTableVer = obs.masterTableVersion();
        int localTableVer = obs.localTableVersion();

        std::ostringstream commonPart;  //-- build part of the filename common to both files

        if (localTableVer == 0)                                   //-- bufrdc: if no local tables then uses 00 tables
            commonPart << "00"                                    //-- discipline: 0 = meteorology
                       << std::setw(6) << std::setfill('0') << 0  // subCentre
                       << std::setw(5) << std::setfill('0') << 0  // centre
                       << std::setw(3) << std::setfill('0') << masterTableVer
                       << std::setw(3) << std::setfill('0') << 0  // localTableVer
                       << ".TXT"
                       << std::ends;
        else                    //-- bufrdc: need site specific tables
            commonPart << "00"  //-- discipline: 0 = meteorology
                       << std::setw(6) << std::setfill('0') << subCentre
                       << std::setw(5) << std::setfill('0') << centre
                       << std::setw(3) << std::setfill('0') << masterTableVer
                       << std::setw(3) << std::setfill('0') << localTableVer
                       << ".TXT"
                       << std::ends;
        //-- build 'bufrdc' style Table B name
        std::string table_B_name = prepBufrTableDir_ + "/B" + commonPart.str();

        it = tables.find(table_B_name);  //-- link already created?
        if (it == tables.end())          //-- if not, then create it now
        {
            int ok = symlink(prepBufrTable_B_.c_str(), table_B_name.c_str());
            if (ok != 0) {
#ifdef METVIEW
                marslog(LOG_EROR, "Unable to create a link into PrepBUFR Table B, returned %d", ok);
#endif
                std::cerr << "Unable to create a link into PrepBUFR Table B" << std::endl;
                return false;
            }
            //-- build 'bufrdc' style Table D name
            std::string table_D_name = prepBufrTableDir_ + "/D" + commonPart.str();
            ok = symlink(prepBufrTable_D_.c_str(), table_D_name.c_str());
            if (ok != 0) {
#ifdef METVIEW
                marslog(LOG_EROR, "Unable to create a link into PrepBUFR Table D, returned %d", ok);
#endif
                std::cerr << "Unable to create a link into PrepBUFR Table D" << std::endl;
                return false;
            }

            std::cout << " created table link " << table_B_name << std::endl;

            tables.insert(table_B_name);  //-- signal that link now exists
        }
    }
    obsSet_.rewind();
    return true;
}

//______________________________________________________________________
//  BUFR_TABLES was previously set to point to the temporary table dir.
//  Now revert it to point back to the original table dir (or unset if
//  not set previously).
void MvPrepBufrPrep::revertBufrTablesDir()
{
    const int overWrite = 1;
    if (origBufrTableDir_ == cNoBufrTablesDir)
        unsetenv("BUFR_TABLES");
    else
        setenv("BUFR_TABLES", origBufrTableDir_.c_str(), overWrite);
}

//______________________________________________________________________
//
//   TableB_entry
//______________________________________________________________________

//______________________________________________________________________
//  Decode one PrepBUFR Table B descriptor and its definition.
//
//  'bufrdc' read cmd is:
//
//                    1  2  8  9   73 74  98 99 102 103 115 116 ->119
//      READ(YENTRY,'(1X,I6,1x,64x,1x,24x,1x,I3,1x,I12,1x,I3)')
//     1                                         NTABBTR(J),NTABBS (J),
//     1                                         NTABBRV(J),NTABBDW(J)


bool TableB_entry::getEntry(MvObs& obs)
{
    obs.setNextDescriptor();  //-- build descriptor F+XX+YYY
    descriptor_ = obs.stringValue().substr(0, 1);
    obs.setNextDescriptor();
    descriptor_ += obs.stringValue().substr(0, 2);
    obs.setNextDescriptor();
    descriptor_ += obs.stringValue().substr(0, 3);

    obs.setNextDescriptor();  //-- data name: line 1 + line 2
    name_ = obs.stringValue().substr(0, 32);
    obs.setNextDescriptor();
    name_ += obs.stringValue().substr(0, 32);

    obs.setNextDescriptor();  //-- unit name
    unit_ = obs.stringValue().substr(0, 24);

    obs.setNextDescriptor();  //-- scale: sign + abs value
    scale_ = obs.stringValue().substr(0, 1);
    obs.setNextDescriptor();
    scale_ += obs.stringValue().substr(0, 3);

    obs.setNextDescriptor();  //-- reference value: sign + abs value
    refval_ = obs.stringValue().substr(0, 1);
    obs.setNextDescriptor();
    refval_ += obs.stringValue().substr(0, 10);

    obs.setNextDescriptor();  //-- bit field width
    width_ = obs.stringValue().substr(0, 3);

    //-- aki: check  OK...                    //-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    return true;
}
//______________________________________________________________________

void TableB_entry::writeEntry(std::ofstream& fout)
{
    fout << " " << descriptor_
         << " " << name_
         << " " << unit_
         << " " << scale_
         << " " << refval_
         << " " << width_
         << std::endl;
}
//______________________________________________________________________
//
//   TableD_entry
//______________________________________________________________________

//______________________________________________________________________
//  Decode one PrepBUFR Table D descriptor and its definition

bool TableD_entry::getEntry(MvObs& obs)
{
    obs.setNextDescriptor();  //-- build descriptor F+XX+YYY
    descriptor_ = obs.stringValue().substr(0, 1);
    obs.setNextDescriptor();
    descriptor_ += obs.stringValue().substr(0, 2);
    obs.setNextDescriptor();
    descriptor_ += obs.stringValue().substr(0, 3);

    obs.setNextDescriptor();  //-- maybe text...
    long descr = obs.MvObs::currentDescriptor();
    if ((descr / 1000) == 205)    //-- 2'05'yyy: signify character?
        obs.setNextDescriptor();  //-- is extra text => skip

    cnt_ = obs.currentValue();  //-- nr of descriptors in the sequence

    if (cnt_ > 0) {
        for (int d = 0; d < cnt_; ++d) {
            obs.setNextDescriptor();
            descrlist_.push_back(obs.stringValue().substr(0, 6));
        }
    }
    else
        descrlist_.push_back("777777");

    //-- aki: check  OK...                    //-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    return true;
}
//______________________________________________________________________

void TableD_entry::writeEntry(std::ofstream& fout)
{
    fout << " " << descriptor_
         << std::setw(3) << cnt_
         << " " << descrlist_[0]
         << std::endl;

    if (cnt_ > 0) {
        for (int d = 1; d < cnt_; ++d)
            fout << "           " << descrlist_[d] << std::endl;
    }

    descrlist_.clear();
}
//______________________________________________________________________
//
//   MvTableExtract
//______________________________________________________________________

bool MvTableExtract::initTableFiles(std::string& prepBufrTable_B, std::string& prepBufrTable_D)
{
    //-- use previously copied aux table file and open it in append mode
    table_B_.open(prepBufrTable_B.c_str(), std::ios::app);  //-- append to aux table B
    if (!table_B_) {
#ifdef METVIEW
        marslog(LOG_EROR, "Unable to open file %s", prepBufrTable_B.c_str());
#endif
        std::cerr << "Unable to open file " << prepBufrTable_B << std::endl;
        return false;
    }

    //-- use previously copied aux table file and open it in append mode
    table_D_.open(prepBufrTable_D.c_str(), std::ios::app);  //-- append to aux table D
    if (!table_D_) {
#ifdef METVIEW
        marslog(LOG_EROR, "Unable to open file %s", prepBufrTable_D.c_str());
#endif
        std::cerr << "Unable to open file " << prepBufrTable_D << std::endl;
        return false;
    }

    return true;
}
//______________________________________________________________________
//  Extract PrepBUFR Table B and Table D entries into memory, sort the
//  entries ('bufrdc' requirement), and then finally write the sorted
//  entries to PrepBUFR table files B and D.

bool MvTableExtract::extract(std::string& prepBufrTable_B, std::string& prepBufrTable_D)
{
    //-- open/create table B and D files
    if (!initTableFiles(prepBufrTable_B, prepBufrTable_D)) {
        return false;
    }

    std::list<TableB_entry*> tab_B_list;  //-- for collecting PrepBUFR table B entries
    std::list<TableD_entry*> tab_D_list;  //-- for collecting PrepBUFR table D entries

    MvObsSetIterator iter(obsSet_);  //-- ObsSet iterator to select only table msgs
    iter.setMessageType(11);         //-- 11: "BUFR tables, complete replacement or update"
    int msgNum = 0;

    tableMsg_ = iter();  //-- get first BUFR table msg

    while (tableMsg_)  //-- iterate though table msgs
    {
        ++msgNum;  //-- table msg number (for error msgs)

        //-- Each table msg contains three delayed replications (0'31'001):
        //-- 1st is for Table A (we skip), 2nd for Table B, 3rd for Table D

        //-- extract possible table B stuff
        int count_B = (int)tableMsg_.valueByOccurrence(2, 31001);
        for (int b = 0; b < count_B; ++b) {
            auto* entry_B = new TableB_entry;
            if (entry_B->getEntry(tableMsg_))
                tab_B_list.push_back(entry_B);
#ifdef METVIEW
            else
                marslog(LOG_EROR, "Error in extracting PrepBUFR Table B entry %d in msg %d", b + 1, msgNum);
#endif
        }
        //-- extract possible table D stuff
        int count_D = (int)tableMsg_.valueByOccurrence(3, 31001);
        for (int d = 0; d < count_D; ++d) {
            auto* entry_D = new TableD_entry;
            if (entry_D->getEntry(tableMsg_))
                tab_D_list.push_back(entry_D);
#ifdef METVIEW
            else
                marslog(LOG_EROR, "Error in extracting PrepBUFR Table D entry %d in msg %d", d + 1, msgNum);
#endif
        }

        tableMsg_ = iter();  //-- get next BUFR tables msg
    }

    tab_B_list.sort(compare_tab_B_elems);  //-- sort prepBUFR Table B entries

    std::list<TableB_entry*>::iterator bit;  //-- write prepBUFR Table B entries
    for (bit = tab_B_list.begin(); bit != tab_B_list.end(); bit++)
        (*bit)->writeEntry(table_B_);

    tab_D_list.sort(compare_tab_D_elems);  //-- sort prepBUFR Table D entries

    std::list<TableD_entry*>::iterator dit;  //-- write prepBUFR Table D entries
    for (dit = tab_D_list.begin(); dit != tab_D_list.end(); dit++)
        (*dit)->writeEntry(table_D_);

    return true;  //-- <aki> add failure checks...
}
//______________________________________________________________________

#if 0
int main()
{
  MvObsSet       obsSet( "/var/tmp/vesa/Data/bufr/2011-01-17-CPTEC/gdas1.t00z.prepbufr.nr" );
  MvTableExtract extract( obsSet );
  extract.extract();
}
#endif

}  // namespace metview
