#define STRICT
#include <windows.h>
#include <direct.h>

#include <al.h>         
#include <archives.h>
#include <config.h>
#include <isam.h>
#include <mask.h>
#include <prefix.h>
#include <progind.h>
#include <urldefid.h>
#include <utility.h>

///////////////////////////////////////////////////////////
// TProgress_win declaration
///////////////////////////////////////////////////////////


class TProgress_win : public TMask
{                  
  ALWindowsMessage* _monitor;         
  TArchive* _arc;
  
protected:
  static bool cancel_handler(TMask_field&, KEY k); 
  
public:
  ALWindowsMessage* monitor() const { return _monitor; }
  
  TProgress_win(const char* title, TArchive* = NULL);
  ~TProgress_win();
};


///////////////////////////////////////////////////////////
// TArchive
///////////////////////////////////////////////////////////


FILE* TArchive::ask_disk(TFilename& name, int disk, char floppy, bool lettura) const
{     
  TFilename prev(name);
  prev.ext(format("%03d", disk-1));       // File precedente
  bool first = TRUE;
  do
  {                 
    if (first)
    {
      message_box("Inserire il disco %d nel drive %c:", disk, floppy);
      first = FALSE;
    }  
    else
    {
      const bool ok = yesno_box("Inserire il disco %d nel drive %c\n"
                                "Estrarre il disco %d e continuare?", disk, floppy, disk-1);
      if (!ok) return NULL;                          
    }
  } while (fexist(prev));                 // Non facciamo i furbetti!

  name.ext(format("%03d", disk));         // File attuale
  
  FILE* f = NULL;                                                           
  bool retry = TRUE;
  while (retry)
  {               
    f = fopen(name, lettura ? "rb" : "wb");
    if (f == NULL) 
      retry = yesno_box("Il file %s non e' accessibile: riprovare?", (const char*)name);
    else
    {
      setvbuf(f, NULL, _IOFBF, BUFSIZE);
      retry = FALSE; 
    }  
  }  

  return f;
} 

long TArchive::fsize(FILE* f) const
{     
  CHECK(f, "Can't measure NULL file");
  fseek(f, 0, SEEK_END);
  const long s = ftell(f);
  fseek(f, 0, SEEK_SET);
  return s;
} 

int TArchive::build_backup_list(long firm, TString_array& fl) const
{
  fl.destroy();
  if (firm < 0L)
  {                     
    fl.add(firm2dir(0L));
    TLocalisamfile ditte(LF_NDITTE);
    for (int err = ditte.first(); err == NOERR; err = ditte.next())
    {
      const char* dir = firm2dir(ditte.get_long("CODDITTA"));
      if (fexist(dir))
        fl.add(dir);
    }  
    
    TFilename name(firm2dir(-1));     // __ptprf
    name.add("config");               // Aggiungi configurazioni
    if (fexist(name))
      fl.add(name);                                                
  }
  else
    fl.add(firm2dir(firm));
  
  return fl.items();  
}


// Costruisce la lista delle directory da scompattare
int TArchive::build_restore_list(long firm, char floppy, TString_array& fl) const
{   
  if (firm < 0)                                       // Crea lista automaticamente
  {      
    TFilename name("a:/backup.ini"); name[0] = floppy;
    TConfig ini(name);
    const int max = ini.list_paragraphs(fl);          // Lista degli archivi
    for (int i = max-1; i >= 0; i--)
    {
      const int disk = ini.get_int("Disk", fl.row(i));
      if (disk == 1) 
      {
        firm = atol(fl.row(i));
        fl.add(firm2dir(firm), i);                    // Aggiungi gli archivi che iniziano qui
      }
      else fl.destroy(i, TRUE);                       // Elimina gli archivi che non iniziano qui
    } 
    
    name = firm2dir(-1);                              // __ptprf
    name.add("config");                               // Aggiungi configurazioni
    fl.add(name);                                                
  }
  else  
  {
    fl.destroy();
    fl.add(firm2dir(firm));                           // Inserisci solo una ditta (o COM)
  }
  
  return fl.items();    
}


bool TArchive::fsplit(const char* filename, char floppy, const char* desc) const
{               
  const TFilename from(filename);          // File da splittare

  FILE* i = fopen(from, "rb"), *o = NULL;
  if (i == NULL) return error_box("Impossibile aprire il file '%s'", from);
  setvbuf(i, NULL, _IOFBF, BUFSIZE);
  
  const long tot = fsize(i);  
  long       scritti=0;
  
  TFilename work;
  work << floppy << ":/" << from.name();    // File su dischetto

  TString256 msg("Archiviazione di "); msg << work << " (" << (tot/1024) << "K)";
  TProgind w(tot, msg, TRUE, TRUE, 40);
  
  int disk = 0;        
  TString buffer(BUFSIZE);
  
  bool ok = TRUE;
  while (ok)
  {
    const size_t letti = fread((char*)(const char*)buffer, 1, BUFSIZE, i);
    
    if (o != NULL)
      ok = (letti > 0) ? (fwrite((char*)(const char*)buffer, letti, 1, o) == 1) : TRUE;
    else
      ok = FALSE;  
    
    if (!ok) 
    { 
      
      if (o != NULL)
      {
        chsize(fileno(o),scritti);  // Must truncate output file or some information will be rewritten on next disk!
        scritti = 0;                // Reset count bytes written to disk.
        fclose(o);
      }
      o = ask_disk(work, ++disk, floppy, FALSE);
      ok = o != NULL;                                               
      if (ok) 
      { 
        TFilename parag(work.name()); parag.ext("");
        TFilename ini("a:/backup.ini"); ini[0] = floppy;
        
        if (fexist(ini)) 
        {
          TConfig c(ini, parag);
          const int d = c.get_int("Disk");
          if (d == disk)
          {
            ok = yesno_box("Il disco %d contiene gia' un backup del direttorio %s del %s"
                           "\nSi desidera continuare?", 
                           disk, (const char*)parag, (const char*)c.get("Date"));
          }    
        }  
        else
        {
          FILE* i = fopen(ini, "w");    // Crea il file backup.ini per evitare messaggi
          fclose(i);
        }
        if (!ok) break;
        
        TConfig c(ini, parag);
        const char* oggi = TDate(TODAY).string();
        c.set("Size", tot , NULL, TRUE); 
        c.set("Disk", disk, NULL, TRUE);
        c.set("Description", desc, NULL, TRUE);
        c.set("Date", oggi, NULL, TRUE);
        
        ok = (letti > 0) ? (fwrite((char*)(const char*)buffer, letti, 1, o) == 1) : TRUE;
        if (!ok) error_box("Impossibile scrivere i dati sul dischetto");
        else 
          scritti +=letti;
      }  
    }
    else
      scritti += letti;
    w.addstatus(letti);
    if (ok) 
      ok = !w.iscancelled();
    if (letti < BUFSIZE) 
      break;
  }    

  fclose(i);
  if (o != NULL) fclose(o);

  return ok;
}


bool TArchive::fbuild(const char* filename, char floppy) const
{               
  const TFilename work(filename);

  FILE* o = fopen(work, "wb"), *i = NULL;
  if (o == NULL) 
    return error_box("Impossibile creare il file '%s'", (const char*)work);
  setvbuf(o, NULL, _IOFBF, BUFSIZE);

  long totale = 0L;                        // Bytes letti
  long max = 1440000L;                     // Bytes da leggere
  TFilename name;                          // Nome del file su dischetto
  name << floppy << ":/" << work.name();

  TString256 msg("Ripristino da "); msg << name;
  TProgind w(max, msg, TRUE, TRUE, 32);
  
  int disk = 0;        
  TString buffer(BUFSIZE);
  
  bool ok = TRUE;
  while (ok)
  {       
    size_t letti = 0;
    if (i != NULL)
    {
      letti = fread((char*)(const char*)buffer, 1, BUFSIZE, i);
      ok = letti > 0;
    }  
    else
      ok = FALSE;  
    
    if (!ok)       // Richiedi nuovo disco
    {
      if (i != NULL) fclose(i);
      i = ask_disk(name, ++disk, floppy, TRUE);
      ok = i != NULL;                                               
      if (ok) 
      {            
        if (disk == 1)
        {                                            
          TFilename ini("a:/backup.ini"); ini[0] = floppy;
          if (fexist(ini)) 
          {
            TFilename parag(name.name()); parag.ext("");
            TConfig c(ini, parag);
            max = c.get_long("Size");
          }
          else ok = yesno_box("Manca il file %s: continuare ugualmente?", (const char*)ini);
        }                                    
        w.setmax(max);
        
        if (ok)   // Leggi primo blocco di bytes
        {
          letti = fread((char*)(const char*)buffer, 1, BUFSIZE, i);
          ok = letti > 0;
        }  
      }  
    }
    
    if (ok)       // La lettura e stata fatta bene
    {
      ok = fwrite((char*)(const char*)buffer, 1, letti, o) == letti;
      if (ok) 
      {
        w.addstatus(letti);   
        totale += letti;
        ok = !w.iscancelled();
      }
      else error_box("Impossibile scrivere i dati sul file %s", (const char*)work);
    }         
    
    if (!ok || totale == max)      // Esci in caso di errore o se hai finito
      break;
  }    

  fclose(o);
  if (i != NULL) fclose(i);

  return ok;
}

bool TArchive::backup(const char* dir, char floppy, const char* desc, bool pr_set)
{                                      
  const TString16 old(prefix().name());
  if (pr_set)
    prefix().set(NULL);
  
  xvt_fsys_save_dir();
  chdir(dir);
  
  const TFilename d(dir);
  const TString16 name(d.name());
  TFilename work; work.tempdir(); work.add(name); work.ext("gal");

  _arc = new ALArchive(work);
  
  TString256 title("Archiviazione di "); title << name;
  TProgress_win w(title, this);
  ALEntryList list(w.monitor());
  _arc->AddWildCardFiles(list, "*.*");
  
  w.open_modal();
  _arc->Create( list );
  w.close_modal();
  
  bool ok = _arc->mStatus == AL_SUCCESS;
  if (ok)
    ok = fsplit(work, floppy, desc);
  else  
    error_box("Compressione degli archivi errata o incompleta:\n%s", 
              _arc->mStatus.GetStatusDetail());

  delete _arc; _arc = NULL;
  
  remove(work);
  xvt_fsys_restore_dir();
  if (pr_set)
    prefix().set(old);
  
  return ok;
}

bool TArchive::backup(long firm, char floppy, const char* desc, bool pr_set)
{                     
  TString_array fl;
  const int num_ditte = build_backup_list(firm, fl);
  
  bool ok = TRUE;
  for (int f = 0; f < num_ditte; f++)
  {                                  
    ok = backup(fl.row(f), floppy, desc, pr_set);
    if (!ok) break;
  }
  
  return ok;  
}

bool TArchive::restore(const char* dir, char floppy, bool tmp, bool pr_set)
{                 
  TFilename work;
  if (tmp) 
    work.tempdir();
  else 
    work = dir;
  
  TFilename output(dir); 
  output = output.name(); output.ext("gal");

  if (!yesno_box("Attenzione l'archivio sul disco %c: verra' ripristinato\n"
                 "nel direttorio %s. Continuare?", floppy, (const char*)work))
    return FALSE;             
  
  xvt_fsys_save_dir();
  bool ok = chdir(work) == 0;
  if (!ok) 
  {
    ok = yesno_box("Non esiste il direttorio %s: si desidera crearlo?", (const char*)work);
    if (ok) 
    {
      make_dir(work);
      ok = chdir(work) == 0;
    }  
    if (!ok)
      return error_box("Impossibile accedere a %s", (const char*)work);
  }  

  const TString16 old(prefix().name());
  if(pr_set)
    prefix().set(NULL);
  
  ok = fbuild(output, floppy);

  if (ok)
  {
    _arc = new ALArchive(output);

    TProgress_win w("Ripristino", this);
    ALEntryList list(w.monitor());
    _arc->ReadDirectory(list);
    
    w.open_modal();
    _arc->Extract( list );
    
    ok = _arc->mStatus == AL_SUCCESS;
    if (!ok) error_box("Ripristino degli archivi errato o incompleto:\n%s", 
                       _arc->mStatus.GetStatusString());
    w.close_modal();    
    delete _arc; _arc = NULL;
    
    remove(output);
  }
  
  xvt_fsys_restore_dir();
  if(pr_set)
    prefix().set(old);

  return ok;
}

bool TArchive::restore(long firm, char floppy, bool temp, bool pr_set)
{
  TString_array fl;
  const int num_ditte = build_restore_list(firm, floppy, fl);
  
  bool ok = TRUE;
  for (int f = 0; f < num_ditte; f++)
  {                                  
    ok = restore(fl.row(f), floppy, temp, pr_set);
    if (!ok) break;
  }
  
  return ok;  

}

void TArchive::stop_job()
{
  if (_arc != NULL)
    _arc->GetStorageObject()->mStatus.SetError(AL_USER_ABORT, "Interrotto dall'utente");
}

///////////////////////////////////////////////////////////
// TProgress_win implementation
///////////////////////////////////////////////////////////

TProgress_win::TProgress_win(const char* title, TArchive* arc)
: TMask(title, 1, 60, 5), _arc(arc)
{ 
  WINDOW wtxt = add_string(101, 0, "", -11, 1, 56, "D"); 
  WINDOW wnum = add_number(102, 0, "", -11, 2, 4, "D"); 
  WINDOW wcan = add_button(DLG_USER, 0, "#102Cancel", -11, 3, 10, 2); 
  set_handler(DLG_USER, cancel_handler);

#if XVT_OS == XVT_OS_WIN
  HWND txt = (HWND)xvt_vobj_get_attr(wtxt, ATTR_NATIVE_WINDOW);
  HWND num = (HWND)xvt_vobj_get_attr(wnum, ATTR_NATIVE_WINDOW);
  _monitor = new ALWindowsMessage(AL_MONITOR_OBJECTS, txt, AL_SEND_RATIO, num);
#endif                      
}                   

TProgress_win::~TProgress_win()
{  
  if (_monitor) delete _monitor;
}                     

bool TProgress_win::cancel_handler(TMask_field& f, KEY k)
{
  if (k == K_SPACE) 
  {
    TProgress_win& m = (TProgress_win&)f.mask();
    if (m._arc != NULL)
      m._arc->stop_job();
  }  
  return TRUE;
}


