#include <time.h>

#include <applicat.h>
#include <colors.h>
#include <config.h>
#include <dongle.h>
#include <scanner.h>
#include <prefix.h>
#include <utility.h>

#include <extcdecl.h>   // GetPrawinName

bool TConfig::add_line(const TString& l)
{
  const int ind = l.find('=');
  if (ind < 0)
    return FALSE;
 
  TString256 key = l.left(ind); key.trim();
  TString val = l.mid(ind+1); val.trim();

  if (val[0] == '%')
  {
    if (val == "%yr%")
    {
      time_t ora; time(&ora);
      struct tm * oggi = localtime(&ora);
      if (oggi != NULL)
        val.format("%04d", 1900 + oggi->tm_year);
      else
        NFCHECK("Impossibile reperire la data corrente del sistema.");
    }
    else
    {
      if (val == "%frm%") 
        val.format("%05ld", prefix().get_codditta());
    }
  }
  // sostituzione abilitata
  return _data.add(key,val,TRUE);
}

// @doc EXTERNAL

// @mfunc Legge i dati del paragrafo
//
// @rdesc Ritorna i seguenti valori:
//
// @flag TRUE | Se il paragrafo c'ere
// @flag FALSE | Se il pragarafo non e' esitente
bool TConfig::_read_paragraph()

  // @comm Legge il contenuto di tutte le variabili del paragrafo attivo
{
  bool itwas = FALSE;
  _data.destroy();
  TScanner scan(_file);
  if (scan.paragraph(_paragraph))
  {
    itwas = TRUE;
    // populate array
    while(TRUE)
    {
      const TString& l = scan.line();
      if (l.empty() || l[0] == '[') break;           // Fine paragrafo
      if (l[0] == '#' || l[0] == '/') continue;      // Riga di commento
      add_line(l);
    }
  }
  return itwas;
}

// @doc EXTERNAL

// @mfunc Scrive i dati del paragrafo
void TConfig::_write_paragraph(
  ofstream& out) // @parm Indirizzo dell'utput sul quale scrivere il paragrafo

  // @comm Scrive sullo stream <p>out le variabili del paragrafo attivo.
{
  if (_data.items() > 0)        // Inutile scrivere paragrafi vuoti!
  {
    out << '[' << _paragraph << ']' << endl;
/*
    _data.restart();
    for (THash_object* o = _data.get_hashobj(); o; o = _data.get_hashobj())
      out << o->key() << " = " << (TString&)(o->obj()) << '\n';
*/
    TString_array a; list_variables(a, FALSE,_paragraph,TRUE); // get array sorted by varname
    for (int i = 0; i < a.items(); i++)
    {
      TToken_string& name = a.row(i);
      out << name << " = ";
      out << get(name) << endl;
    }

    out << endl;
  }
}

void TConfig::_write_file()
{
  if (_write_protected)
    return;

  TFilename temp;
  temp.temp("cnf");
  ofstream out(temp);
  if (!out.good())
  {        
    NFCHECK("Impossibile scrivere %s per aggiornare %s", 
            (const char*)temp, (const char*)_file);
    return;
  }

  bool skip = FALSE, done = FALSE, skip_empty = TRUE;
    
  if (_file.exist())  
  {
    ifstream in(_file, ios::in | ios::nocreate, filebuf::sh_read);       
    if (in.good()) 
    {
      TString l(1024);
      TString cnf; cnf << '[' << _paragraph << ']';
      while (!in.eof())
      {
        in.getline(l.get_buffer(), l.size());
        l.trim();
    
        if (cnf == l)
        {
          // write paragraph and all variables
          _write_paragraph(out);
          skip = skip_empty = done = TRUE;
        }
        else
        {
          if (skip)
            skip = l[0] != '[';
          if (!skip)
          {
            const bool empty = l.empty();
            if (!empty || !skip_empty)
              out << l << endl;
            skip_empty = empty;
          }
        }
      }
    }
    else
    {
      NFCHECK("Impossibile aggiornare il file %s", (const char*)_file);
      return;
    }
  }
  
  // new paragraph
  if (!done) 
    _write_paragraph(out);
  out.close();

  if (fexist(_file))
  {
    while (access(_file, 02) != 0)
      message_box("Il file %s e' gia' in uso", (const char*)_file);
  }
  fcopy(temp, _file);      // Copia dalla tempdir al nuovo .ini
  ::remove(temp);          // Cancella file temporaneo
}

bool TConfig::set_paragraph(const char* section)
{
  bool ok = TRUE;
  if (section != NULL && _paragraph != section)
  {
    if (_dirty)
      _write_file();
    _paragraph = section;
    _dirty = FALSE;
    _ispresent = _read_paragraph();
     ok = _ispresent;
  }
  return ok;
}

// @doc EXTERNAL

// @mfunc Controlla se esite una variabile nel paragrafo attivo
//
// @rdesc Ritorna i seguenti valori:
//
// @flag TRUE | Se la variabile esite
// @flag FALSE | Se la variabile non esite
bool TConfig::exist(
  const char* var,  // @parm Nome della variabile
  int index)        // @parm Indice dell'elemento dell'array (default -1)

  // @comm Se <p index> e' <gt>= 0 viene costruito il nome dell'elemento
  //       dell'array da cercare, diversamente viene cercata la variabile
  //       normale passata in <p var>.
{
  if (index >= 0)
  {
    TString key(80);
    key << var << '(' << index << ')';
    return _data.is_key(key);
  }
  return _data.is_key(var);
}

// @doc EXTERNAL

// @mfunc Elimina una variabile dal paragrafo corrente
//
// @rdesc Ritorna i seguenti valori:
//
// @flag TRUE | Se la variabile esiteva
// @flag FALSE | Se la variabile non esiteva
bool TConfig::remove(
  const char* var,  // @parm Nome della variabile
  int index)        // @parm Indice dell'elemento dell'array (default -1)

// @comm Se <p index> e' <gt>= 0 viene costruito il nome dell'elemento
//       dell'array da cercare, diversamente viene cercata la variabile
//       normale passata in <p var>.
{
  TString key(var);
  if (index >= 0)
    key << '(' << index << ')';
  const bool ok = _data.remove(key);
  if (ok) _dirty = TRUE;
  return ok;
}

void TConfig::remove_all()
{
  if (_data.items() > 0)
  {
    _data.destroy();
    _dirty = TRUE;
  }
}


// @doc EXTERNAL

// @mfunc Ritorna il valore della variabile nella sezione corrente o in
//        quella specificata
//
// @rdesc Ritorna la stringa contenuta nella variabile, se questa esiste, altrimenti
//        il valore di default che dovrebbe assumere determinato dal parametro
//        <p def>
const TString& TConfig::get(
  const char* var,     // @parm Variabile della quale ritornare il valore
  const char* section, // @parm Sezione della variabile (default NULL)
  int index,           // @parm Eventuale indice della varaibailie (default -1)
  const char* def)     // @parm Valore default della varaibile (default "")

  // @comm Passando <p index> <gt>= 0 viene appeso al nome della variabile per
  //       implementare un array.
  //       <nl>Il paragrafo passato in <p section> diventa quello attivo.
  //
  // @xref <mf TConfig::get_long> <mf TConfig::get_int> <mf TConfig::get_bool>
  //       <mf TConfig::get_color>
{
  if (section)                            // Cambia paragrafo se necessario
    set_paragraph(section);
  
  const TString* val;
  if (index >= 0)
  {
    TString v(80);
    v << var << '(' << index << ')';
    val = (TString*)_data.objptr(v);
  }
  else
    val = (TString*)_data.objptr(var);

  if (val == NULL)                        // Se non la trova inserisci il default
  {      
    if (def && *def)   
    {
      set(var, def, section, TRUE, index);
      val = &get(var, NULL, index);
    }
    else
      val = &EMPTY_STRING;
  }

  return *val;
}

// @doc EXTERNAL

// @mfunc Ritorna il valore della variabile nella sezione corrente o in
//        quella specificata
//
// @rdesc Ritorna il numero contenuto nella variabile, se questa esiste, altrimenti
//        il valore di default che dovrebbe assumere determinato dal parametro
//        <p def>
long TConfig::get_long(
  const char* var,     // @parm Variabile della quale ritornare il valore
  const char* section, // @parm Sezione della varaibile (default NULL)
  int index,           // @parm Eventuale indice della varaibailie (default -1)
  long def)            // @parm Valore default della varaibile (default 0L)

  // @comm Passando <p index> <gt>= 0 viene appeso al nome variabile per
  //       implementare un array.
  //       <nl>Il paragrafo passato in <p section> diventa quello attivo.
  //
  // @xref <mf TConfig::get> <mf TConfig::get_int> <mf TConfig::get_bool>
  //       <mf TConfig::get_color>
{
  const char* n = get(var,section,index);
  if (*n)
    def = atol(n);
  else if (def != 0)
  {
    TString16 d; d << def;
    set(var, d, section, TRUE, index);
  }
  return def;

}

// @doc EXTERNAL

// @mfunc Ritorna il valore della variabile nella sezione corrente o in
//        quella specificata
//
// @rdesc Ritorna il primo carattere della variabile, se questa esiste, altrimenti
//        il valore di default che dovrebbe assumere determinato dal parametro
//        <p def>
char TConfig::get_char(
  const char* var,     // @parm Variabile della quale ritornare il valore
  const char* section, // @parm Sezione della varaibile (default NULL)
  int index,           // @parm Eventuale indice della varaibailie (default -1)
  char def)            // @parm Valore default della varaibile (default ' ')

  // @comm Passando <p index> <gt>= 0 viene appeso al nome variabile per
  //       implementare un array.
  //       <nl>Il paragrafo passato in <p section> diventa quello attivo.
  //
  // @xref <mf TConfig::get> <mf TConfig::get_int> <mf TConfig::get_bool>
  //       <mf TConfig::get_color>
{
  const char* n = get(var,section,index);
  if (*n)
    def = *n;
  else if (!isspace(def))            
  {
    const char d[2] = { def, '\0' };
    set(var, d, section, TRUE, index);
  }
  return def;
}

// @doc EXTERNAL

// @mfunc Ritorna il valore della variabile nella sezione corrente o in
//        quella specificata
//
// @rdesc Ritorna l'intero contenuto nella variabile, se questa esiste, altrimenti
//        il valore di default che dovrebbe assumere determinato dal parametro
//        <p def>
int TConfig::get_int(
  const char* var,     // @parm Variabile della quale ritornare il valore
  const char* section, // @parm Sezione della varaibile (default NULL)
  int index,           // @parm Eventuale indice della varaibailie (default -1)
  int def)             // @parm Valore default della varaibile (default 0)

  // @comm Chiama la funzione <mf TConfig::get_long> e ne ritorna un intero.
  //       <nl>Passando <p index> <gt>= 0 viene appeso al nome variabile per
  //       implementare un array.
  //       <nl>Il paragrafo passato in <p section> diventa quello attivo.
  //
  // @xref <mf TConfig::get> <mf TConfig::get_long> <mf TConfig::get_bool>
  //       <mf TConfig::get_color>
{
  return (int)get_long(var, section, index, def);
}

// @doc EXTERNAL

// @mfunc Ritorna il valore della variabile nella sezione corrente o in
//        quella specificata
//
// @rdesc Ritorna i seguenti valori
//
// @flag TRUE | Se la varabile e' settata con X
// @flag FALSE | Se la varabile nen e' settata con X
// @flag <p def> | Se la varabile non esiste
bool TConfig::get_bool(
  const char* var,     // @parm Variabile della quale ritornare il valore
  const char* section, // @parm Sezione della varaibile (default NULL)
  int index,           // @parm Eventuale indice della varaibailie (default -1)
  bool def)            // @parm Valore default della varaibile (default FALSE)

  // @comm Viene chiamata la funzione <mf TConfig::get>.
  //       <nl>Passando <p index> <gt>= 0 viene appeso al nome variabile per
  //       implementare un array.
  //       <nl>Il paragrafo passato in <p section> diventa quello attivo.
  //
  // @xref <mf TConfig::get> <mf TConfig::get_long> <mf TConfig::get_int>
  //       <mf TConfig::get_color>
{
  const char* d = def ? "X" : "";
  TString& s = (TString&)get(var, section, index, d);
  s.upper();
  return s != "" && (s == "X" || s == "Y" || s == "1" || s == "ON" || s == "YES" || s == "OK" || s == "TRUE");
}

// @doc EXTERNAL

// @mfunc Ritorna il valore del colore settato nella variabile nella
//        sezione corrente o in quella specificata
COLOR TConfig::get_color(
  const char* var,     // @parm Variabile della quale ritornare il valore
  const char* section, // @parm Sezione della varaibile (default NULL)
  int index,           // @parm Eventuale indice della varaibailie (default -1)
  COLOR def)           // @parm Valore default della varaibile (default 0)

  // @comm Passando <p index> <gt>= 0 viene appeso al nome variabile per
  //       implementare un array.
  //       <nl>Il paragrafo passato in <p section> diventa quello attivo.
  //
  // @xref <mf TConfig::get> <mf TConfig::get_long> <mf TConfig::get_int>
  //       <mf TConfig::get_bool>
{
  const char* c = get(var, section, index);
  if (*c)
  {
    TToken_string s(c, ',');
    const byte r = (byte)s.get_int();
    const byte g = (byte)s.get_int();
    const byte b = (byte)s.get_int();
    def = RGB2COLOR(r, g, b);
  }
  else
  {
    TString16 d;
    d.format("%d,%d,%d",
             XVT_COLOR_GET_RED(def), XVT_COLOR_GET_GREEN(def), XVT_COLOR_GET_BLUE(def));
    set(var, d, section, TRUE, index);
  }

  return def;
}

// @doc EXTERNAL

// @mfunc Setta la variabile nella sezione corrente o specificata
//
// @rdesc Ritorna i seguenti valori:
//
// @flag TRUE | Se la variabile era gia' esistente
// @flag FALSE | Se la variabile non era gia' esistente
bool TConfig::set(
  const char* var,     // @parm Nome della variabile da settare
  const char* value,   // @parm Stringa da assegnare alla variabile
  const char* section, // @parm Nome del paragrafo a cui appartiene la variabile
  bool force,          // @parm Per la creazione di una variabile inesistente
  int index)           // @parm Eventuale indice della variabile
  // @parm long | value | Valore da assegnare alla variabile
{
  // @syntax set(const char* var, const char* value, const char* section, bool force, int index);
  // @syntax set(const char* var, long value, const char* section, bool force, int index);
  //
  // @comm Se <p force> == TRUE crea il paragrafo e la variabile se non esistono;
  //       altrimenti da' errore.
  //       <nl>Passando <p index> <gt>= 0 viene appeso al nome variabile per
  //       implementare un array.
  //       <nl>Il paragrafo passato in <p section> diventa quello attivo.

  if (section && *section)
    set_paragraph(section);

/*
  const bool itwas = exist(var, index);

  if (itwas && !force)
    error_box("Tentativo di ridefinizione simbolo: %s(%d)", var, index);
  else
  {
    TString256 vvar(var); if (index >= 0) vvar << '(' << index << ')';
    _data.add(vvar, new TString(value), force);
    _dirty = TRUE;
  }
*/
  TString256 key(var);
  if (index >= 0)
    key << '(' << index << ')';
  TString* val = (TString*)_data.objptr(key);
  const bool itwas = val != NULL;

  if (itwas && !force)
    error_box("Tentativo di ridefinizione simbolo: %s", (const char*)key);
  else
  {
    if (itwas)
    {
      const TFixed_string str(value);
      // Se la variabile esisteva ed aveva un valore diverso ...
      if (*val != str && !(str.blank() && val->empty()))
      {
        *val = str;     // ... allora la sostituisco ...
        val->trim();
        _dirty = TRUE;  // ... e metto a dirty.
      }
    }
    else
    {
      // Se la variabile non esisteva allora la aggiungo e metto a dirty.
      val = new TString(value);
      val->trim();
      _data.add(key, val, TRUE);
      _dirty = TRUE;
    }
  }
  return itwas;
}

bool TConfig::set(const char* var, long value, const char* section,
                  bool force, int index)
{
  TString16 t; t << value;
  return set(var,t,section,force,index);
}

bool TConfig::set_color(const char* var, COLOR col, const char* section,
                        bool force, int index)
{
  TString16 t;
  t.format("%d,%d,%d", XVT_COLOR_GET_RED(col), XVT_COLOR_GET_GREEN(col), XVT_COLOR_GET_BLUE(col));
  return set(var,t,section,force,index);
}


// @doc EXTERNAL

// @mfunc Ritorna quanti elementi dell'array nominato sono presenti nella
//          sezione indicata.
word TConfig::items(
  const char* var,     // @parm Nome dell'array
  const char* section) // @parm Sezione indicata

  // @comm Il paragrafo passato in <p section> diventa quello attivo.
  //       <nl>Possono esserci dei "buchi" causati da set() errate
{
  if (section) set_paragraph(section);
  for (int cnt = 0; exist(var, cnt); cnt++);
  return cnt;
}

// @doc EXTERNAL

// @mfunc Inizializza il paragrafo leggendo dal file i dati
void TConfig::init(
  const char *fn,  // @parm Nome del file da leggere
  const char* pa,  // @parm Nome del paragrafo da utilizzare
  bool warning)    // @parm Segnala assenza del file

  // @comm Apre il file <p fn> e cerca il paragrafo <p pa>. Se il file non esiste
  //       viene creato con il paragrafo passato.
{
  _file = fn;
  _paragraph = pa;
  _dirty = FALSE;
  _write_protected = FALSE;
  
  if (!_file.exist())
  {
    if (warning)
      warning_box("Creazione del file di configurazione %s", fn );
    ofstream c(fn);
    c.close();
  }

  if (_paragraph.blank())
  { 
    _paragraph = main_app().name();
    _paragraph.cut(2);         
    _paragraph.lower();
  }

  _ispresent = _read_paragraph();
}

int TConfig::list_paragraphs(TString_array& pl)
{
  TScanner s(_file);
  pl.destroy();
  while (s.line().not_empty())
  {
    if (s.token()[0] == '[')
    {
      TToken_string* p = new TToken_string(s.token());
      p->strip("[]");
      pl.add(p);
    }
  }
  return pl.items();
}

HIDDEN int compare_ini_variables(const TObject**o1, const TObject**o2)
{
  const TString* s1 = (const TString*)*o1;
  const TString* s2 = (const TString*)*o2;
  const int p1 =  s1->find('(');
  if (p1 < 0)
    return strcmp(*s1, *s2);
  else
  {
    // variabile tipo XX(i)
    int result= strncmp(*s1, *s2,p1);
    if (result==0)
    {
      if (s2->find('(')<0)
        result = 1;
      else
      {
        CHECKS(s1->find(')')>=0,"Errore: parentesi non chiusa in ",(const char *)(*s1));
        CHECKS(s2->find(')')>=0,"Errore: parentesi non chiusa in ",(const char *)(*s2));
        const int i1=atoi(s1->sub(p1+1,s1->len()-1));
        const int i2=atoi(s2->sub(p1+1,s2->len()-1));
        result = i1-i2;
      }
    }
    return result;
  }
}


int TConfig::list_variables(TString_array& vl, bool value, const char* section, const bool sort)
{              
  set_paragraph(section);
  vl.destroy();
  _data.restart();

  for (int i = 0; i < _data.items(); i++)
  {
    THash_object* o = _data.get_hashobj();
    TToken_string* t = new TToken_string(o->key());
    if (value) t->add((TString&)(o->obj()));
    vl.add(t);
  }

  if (sort)
    vl.TArray::sort(compare_ini_variables);

  return vl.items();
}

TAssoc_array& TConfig::list_variables(const char* section)
{
  set_paragraph(section);
  return _data;
}

int TConfig::for_each_paragraph(CONFIG_CALLBACK cfgcb, void* jolly)
{
  int count = 0;
  TScanner scanner(_file);

  _paragraph.cut(0);
  bool needs_call = FALSE;

  while (scanner.line().not_empty())
  {
    const TString& riga = scanner.token();
    if (riga[0] == '[')
    {
      count++;
      if (needs_call)
      {
        needs_call = FALSE;
        if (cfgcb && cfgcb(*this, jolly))
          break;
      }
      _paragraph = riga;
      _paragraph.strip("[]");
      _data.destroy();
      needs_call = TRUE;
    }
    else
      add_line(riga);
  }
  
  if (needs_call && cfgcb)
    cfgcb(*this, jolly);

  return count;
}

TConfig::TConfig(int which_config, const char* paragraph)
{
  switch (which_config)
  {
  case CONFIG_DITTA:
    _file = firm2dir(prefix().get_codditta());
    _file.add("prassid.ini");
    if (!fexist(_file) && fexist("prassid.ini")) 
      fcopy("prassid.ini", _file);   
    break;
  case CONFIG_STUDIO:
  case CONFIG_USER:
  case CONFIG_STAMPE:
    _file = firm2dir(-1);       // Directory dati
    _file.add("config");        // Directory config
    if (!_file.exist())         // Creala se necessario
      make_dir(_file);

    switch (which_config)
    {
    case CONFIG_STUDIO:
      _file.add("prassis.ini");
      if (!_file.exist() && fexist("prassis.ini"))
        fcopy("prassis.ini", _file);
      break;
    case CONFIG_STAMPE:
      _file.add("print.ini");
      break;
    case CONFIG_USER:
      {
        TString16 u = user();
        if (u.blank())
          u = ::dongle().administrator();
        else
          u.upper();
        _file.add(u);
        _file.ext("ini");
        if (u != "PRASSI" && !_file.exist()) 
        {
          TFilename prassi = _file.path();
          prassi.add("prassi.ini");
          fcopy(prassi, _file);
        }
      }
      break;
    default:
      break;
    }
    break;
  case CONFIG_FCONV:
    _file = "fconv.ini";
    break;
  case CONFIG_INSTALL:
    _file = CGetPrawinName();
    break;
  case CONFIG_GODMODE:
    _file = firm2dir(0);
    _file.add("gm.ini");
    if (paragraph == NULL || *paragraph == '\0')
      paragraph = "GODMODE";
    break;  
  default:
    _file = "prassi.ini";
    NFCHECK("Chi usa prassi.ini?");
    break;
  }  
#ifdef DBG
  init(_file, paragraph, TRUE);  // Segnala warning solo in debug
#else  
  init(_file, paragraph, FALSE);
#endif      
}

TConfig::TConfig(const char *fn, const char* pa)
{ init(fn, pa, FALSE); }


TConfig::~TConfig()
{
  // il distruttore riscrive il file con le modifiche se necessario
  if (_dirty)
    _write_file();
}