// MvPrepBufrPrep.cc

#include <stdlib.h>
#include <string.h>
#include <list>
#include <set>
#include "MvPrepBufrPrep.h"
#include "MvObsSet.h"

const string cNoBufrTablesDir("NO_TABLE_DIR");
const string cCommonNamePart("_prepbufr.txt");
const string cAUX_TABLE_B("prepbufr_table_B.txt");
const 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

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

//______________________________________________________________________

bool
MvPrepBufrPrep::prepareAll()
{
  createPrepBufrTableDir();                 //-- new unique subdir to play with table links

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

  setNewBufrTablesDir();                    //-- (re)assign BUFR_TABLES env.var

  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;
}

//______________________________________________________________________

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

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

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

//______________________________________________________________________
//  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 )
{
  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
             << setw(6) << setfill('0') << subCentre
             << setw(5) << setfill('0') << centre
             << setw(3) << setfill('0') << masterTableVer
             << setw(3) << setfill('0') << localTableVer
             << ".TXT"
             << ends;

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

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

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

  //--  1) copy table file from etc/share and make writable (we need to append PrepBUFR entries)
  string copyTableB = "cp " + etc_auxTable_B + " " + auxTable_B + "; chmod u+w " + auxTable_B + ";";
  cout << copyTableB << endl;
  int ok = 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
     return false;
  }

  //--  1) copy table file from etc/share and make writable (we need to append PrepBUFR entries)
  string copyTableD = "cp " + etc_auxTable_D + " " + auxTable_D + "; chmod u+w " + auxTable_D + ";";
  cout << copyTableD << endl;
  ok = 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
     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.

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

  setenv( "BUFR_TABLES", newEnvVar.c_str(), overWrite );

  cout << "MvPrepBufrPrep::setNewBufrTablesDir: BUFR_TABLES new values is "
       << newEnvVar << endl;
}
//______________________________________________________________________
//  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()
{
  set<string> tables;                       //-- keep track of existing table links
  set<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();

     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
	     << setw(6) << setfill('0') << 0  // subCentre
	     << setw(5) << setfill('0') << 0  // centre
	     << setw(3) << setfill('0') << masterTableVer
	     << setw(3) << setfill('0') << 0  // localTableVer
	     << ".TXT"
	     << ends;
     else                                   //-- bufrdc: need site specific tables
        commonPart << "00"                  //-- discipline: 0 = meteorology
	     << setw(6) << setfill('0') << subCentre
	     << setw(5) << setfill('0') << centre
	     << setw(3) << setfill('0') << masterTableVer
	     << setw(3) << setfill('0') << localTableVer
	     << ".TXT"
	     << ends;
                                            //-- build 'bufrdc' style Table B name
     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
           return false;
        }
                                            //-- build 'bufrdc' style Table D name
        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
           return false;
        }

  cout << " created table link " << table_B_name << 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( ofstream& fout )
{
  fout << " " << descriptor_
       << " " << name_
       << " " << unit_
       << " " << scale_
       << " " << refval_
       << " " << width_
       << 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( ofstream& fout )
{
  fout << " " << descriptor_
       << setw(3) << cnt_
       << " " << descrlist_[0]
       << endl;

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

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

bool
MvTableExtract::initTableFiles( string& prepBufrTable_B, string& prepBufrTable_D )
{
  //-- use previously copied aux table file and open it in append mode
  table_B_.open( prepBufrTable_B.c_str(), 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
     return false;
  }

  //-- use previously copied aux table file and open it in append mode
  table_D_.open( prepBufrTable_D.c_str(), 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
     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( string& prepBufrTable_B, string& prepBufrTable_D )
{
                                            //-- open/create table B and D files
  if( ! initTableFiles( prepBufrTable_B, prepBufrTable_D ) )
  {
     return false;
  }

  list<TableB_entry*> tab_B_list;           //-- for collecting PrepBUFR table B entries
  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)
    {
      TableB_entry* 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)
    {
      TableD_entry* 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

  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

  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
