#include <xvt.h>
#if XVT_OS == XVT_OS_WIN || XVT_OS == XVT_OS_NT
#include <io.h>
#else
#include <stdio.h>
#endif
#include <stdlib.h>

#include <applicat.h>
#include <config.h>
#include <date.h>
#include <scanner.h>
#include <prefix.h>
#include <utility.h>

extern "C"
{
  int rename(const char*, const char*);
};

// @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
    TString key, val;
    for (;;)
    {
      const TString& l = scan.line();
      if (l == "" || l[0] == '[') break;             // Fine paragrafo
      if (l[0] == '#' || l[0] == '/') continue;      // Riga di commento

      const int ind = l.find('=');
      if (ind == -1) 
      {
        error_box("Errore configurazione:\n file %s, vicino alla riga %u\n %s",
                  (const char*)_file, scan.linenum(), (const char*)l);
        continue;
      }
      
      key = l.left(ind);  key.trim();
      val = l.mid(ind+1); val.trim();
      
      if (val[0] == '%')
      {
        if (val == "%yr%") val.format("%04d", TDate(TODAY).year()); else
          if (val == "%frm%") val.format("%05ld", prefix().get_codditta()); 
      }  
      // sostituzione abilitata
      _data.add(key,val,TRUE);
    }
  }
  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 le variabili del paragrafo attivo. Nel caso il paragrafo non
  //       era presente viene aggiunto nel file.
{
  _data.restart();            
  out << '[' << _paragraph << ']' << endl;  
  for (int i = 0; i < _data.items(); i++)
  {
    THash_object* o = _data.get_hashobj();
    out << o->key() << "\t= " << (TString&)(o->obj()) << '\n';
  }
  out << endl;
}

void TConfig::_write_file()

{
  ifstream in(_file);       
  TFilename temp;
  temp.temp("cnf");
  ofstream out(temp);
  
  TString l(1024); 
  TString80 cnf; cnf << '[' << _paragraph << ']';
  
  bool skip = FALSE, done = FALSE;

  while (!in.eof())
  {
    in.getline((char *) (const char *) l, l.size());
    l.trim();

    if (cnf == l)
    {
      // write paragraph and all variables
      _write_paragraph(out);
      skip = TRUE; done = TRUE;
    }
    else 
    { 
      if (skip)  skip = l[0] != '[';
      if (!skip) out << l << '\n';
    }
  }
  // new paragraph
  if (!done) _write_paragraph(out);

  out.close(); in.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
}

void TConfig::set_paragraph(const char* section)
{
  if (section != NULL && section != _paragraph)
  {
    if (_dirty) _write_file();
    _paragraph = section; 
    _dirty = FALSE;
    _read_paragraph();
  }
}

// @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)
  {
    TString80 vvar(var); 
    vvar << '(' << index << ')';
    return _data.is_key(vvar); 
  }
  return _data.is_key(var); 
}

// @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>
TString& TConfig::get(
  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)
  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>
{
  HIDDEN TString256 s;
  
  const char* v = var;
  if (index >= 0)                         // Mette indice tra parentesi
  {          
    s = var;
    s << '(' << index << ')';
    v = s;
  }  
  
  set_paragraph(section);

  if (_data.is_key(v)) 
    s = (TString&)_data[v];
  else           
  {
    s = def;
    if (s.not_empty())
      set(var, def, section, TRUE, index);
  }
  return s;
}

// @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
  {
    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             
  {
    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 TString16 d(def ? "X" : "");
  const TString& s = get(var, section, index, d).upper();
  return s != "" && (s == "X" || s == "ON" || s == "YES" || s == "OK" || s == "TRUE");
}


HIDDEN void RGB_COLOR(COLOR c, int& r, int& g, int& b)
{
  r = int(c >> 16) & 0xFF;
  g = int(c >> 8) & 0xFF;
  b = int(c) & 0xFF;
}

COLOR RGB2COLOR(unsigned char red, unsigned char green, unsigned char blue)
{
  COLOR def = MAKE_COLOR(red, green, blue);

  // Se nel colore non compare l'indice cerca di calcolarlo
  const byte color_index = byte(def >> 12);
  if (color_index < 0x1 || color_index > 0xF)                                
  {
    const COLOR native_color[11] = { COLOR_RED,     COLOR_GREEN,  COLOR_BLUE,  COLOR_CYAN, 
                                     COLOR_MAGENTA, COLOR_YELLOW, COLOR_BLACK, COLOR_DKGRAY,
                                     COLOR_GRAY,    COLOR_LTGRAY, COLOR_WHITE };
    for (int c = 0; c < 11; c++)
    {
      if (def == (native_color[c] & 0x00FFFFFF))    // Confronta solo la terna R,G,B
      {
        def = native_color[c];
        break;
      }  
    }
  }
  
  return def;
}

// @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
  {
    int r, g, b; RGB_COLOR(def, r, g, b);
    TString16 d; d.format("%d,%d,%d", r, g, b);
    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.

  set_paragraph(section);
  TString vvar(var); if (index != -1) vvar << '(' << index << ')';

  bool itwas = _data.is_key(vvar);
  
  if (itwas && !force)
    warning_box("Tentativo di ridefinizione simbolo: %s", (const char*)vvar);
  else
  {
    _dirty = TRUE;
    _data.add(vvar, new TString(value), force);
  }
  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);
}

// @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
{
  set_paragraph(section);
  TString vvar(16);
  for (int cnt = 0; /* uncazzo */ ;cnt++)
  {
    vvar = var; vvar << '(' << cnt << ')';
    if (!_data.is_key(var))
      break;
  }
  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

  // @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; 
  
  if (!fexist(_file))
  {
    warning_box("Creazione del file di configurazione %s", fn );
    ofstream c(fn);
    c.close();
  }  
  
  if (_paragraph.empty())
  {
    _paragraph = main_app().name();
    _paragraph.cut(2);
  }
  _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();
}

int TConfig::list_variables(TString_array& vl, bool value, const char* section)
{              
  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);
  }

  return vl.items();
}

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


TConfig::TConfig(int which_config, const char* paragraph)
{             
  switch (which_config)
  {
  case CONFIG_DITTA:
    _file << main_app().get_firm_dir() << '/' << "prassid.ini";
    if (!fexist(_file)) fcopy("prassid.ini", _file);   
    break;
  case CONFIG_STUDIO:
  case CONFIG_USER:
  case CONFIG_STAMPE:
    _file = firm2dir(-1);       // Directory dati
    _file.add("config");
    if (!fexist(_file))
      make_dir(_file);
    
    switch (which_config)
    {      
    case CONFIG_STUDIO:
      _file.add("prassis.ini"); 
      if (!fexist(_file)) fcopy("prassis.ini", _file);
      break;
    case CONFIG_STAMPE:
      _file.add("print.ini"); 
      break;
    default:
      if (user().not_empty())
        _file.add(user());
      else  
        _file.add("prassi"); 
      _file.ext("ini");
      break;
    }  
    break;
  case CONFIG_FCONV:
    _file = "fconv.ini";
    break;
  case CONFIG_GOLEM:
    _file.add("golem.ini"); 
    break;
  default:
    _file = "prassi.ini"; 
    CHECK(0, "Chi ca$$o usa prassi.ini?");
    break;
  }  

  init(_file, paragraph);
}

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


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