#include <agasys.h>

#include <automask.h>
#include <execp.h>
#include <isam.h>
#include <mailbox.h>
#include <progind.h>
#include <utility.h>

#include "ba2200.h"
#include "ba2201.h"

#include <user.h>
#include <nditte.h>

struct TAFile_info : public TObject
{ 
  TFilename _name;
  long _size;
  int _disk;
  int _last_disk;
};

bool is_real_floppy(const TFilename& path)
{
  return (path[0] == 'A' || path[0] == 'B') && xvt_fsys_is_removable_drive(path);
}

///////////////////////////////////////////////////////////
// Archiving mask
///////////////////////////////////////////////////////////

class TArchive_mask : public TAutomask
{ 
protected:
  virtual bool on_field_event(TOperable_field& o, TField_event e, long jolly);

public:  
  TArchive_mask();
};

TArchive_mask::TArchive_mask() : TAutomask("ba2200") 
{
  TList_field& lf = lfield(F_FLOPPY);
  TToken_string codes, values;  
  TString4 str; 
  int k = 0;
  for (int d = 0; d < 26; d++)
  {
    str.format("%c:", 'A'+d);
    const bool isrem = xvt_fsys_is_removable_drive(str) != 0;
    const bool isfix = !isrem && xvt_fsys_is_fixed_drive(str);
    const bool isnet = !isrem && !isfix && xvt_fsys_is_network_drive(str);
    if (isrem || isfix || isnet)
    {
      codes.add(str);
      values.add(str);
      TToken_string& message = *lf.message(k++, TRUE);
      if (isrem && is_real_floppy(str))
        message.format("CLEAR,%d", F_PATH);
      else                                   
        message.format("ENABLE,%d", F_PATH);
    }
  }
  lf.replace_items(codes, values);
}

bool TArchive_mask::on_field_event(TOperable_field& o, TField_event e, long jolly)
{             
  switch (o.dlg())
  {
  case F_PATH:
    if (e == fe_button)
    {
      TFilename path = get(F_FLOPPY); path << SLASH; path.add(o.get());
      DIRECTORY dir; xvt_fsys_convert_str_to_dir(path, &dir);
      if (xvt_dm_post_dir_sel(&dir) == FL_OK)
      {
        xvt_fsys_convert_dir_to_str(&dir, path.get_buffer(), path.size());
        o.set(path);
        e = fe_modify;
      }
    }
    if (e == fe_modify)
    {     
      TFilename path = o.get();
      if (path[1] == ':')
      {
        set(F_FLOPPY, path.left(2));
        o.set(path.mid(2));
      }
      path = get(F_FLOPPY); path << SLASH; path.add(o.get());
      if (!path.exist())
        return error_box(FR("Percorso non valido: %s"), (const char*)path);
    }
    break;
  case F_DITTA:
    if (e == fe_modify || e == fe_init)
    {                      
       set(F_RAGSOC, !o.empty() ? TR("Tutte le ditte") : TR("Nessuna ditta"));
    }    
    break;
  case F_CODDITTA:
    if (e == fe_modify)
    {                      
      if (o.empty())
        set(F_RAGSOC, o.active() ? TR("Tutte le ditte") : TR("Nessuna ditta"));
    }
    break;
  case F_SALVA:
    if (e == fe_button)
    {
      const long ditta = get_long(F_CODDITTA);
      if (ditta > 0L && !prefix().exist(ditta))
        return error_box(FR("La ditta %ld non esiste"), ditta);
    }
  default:
    break;
  }
  return true;
}

///////////////////////////////////////////////////////////
// Archiving application
///////////////////////////////////////////////////////////

bool TArchive_app::create()
{    
  bool ok = true;

  TIsamfile utenti(LF_USER);
  utenti.open(_excllock);
  for (int err = utenti.first(); err == NOERR; err = utenti.next())
  {                            
    const TString& u = utenti.get(USR_USERNAME);
    if (u != user() && utenti.get_bool(USR_CONNECTED))
#ifdef DBG  
			ok = yesno_box("L'archiviazione non puo' essere effettuata\nse ci sono altri utenti collegati (%s)", (const char*)u);
#else
      ok = error_box(FR("L'archiviazione non puo' essere effettuata\nse ci sono altri utenti collegati (%s)"), (const char*)u);
#endif
  }
  utenti.close();
  
  if (ok)
    ok = TSkeleton_application::create();
  
  return ok;
}

KEY TArchive_app::query(int& mode, long& firm, TFilename& floppy_path, TString& desc) const
{
  TArchive_mask m;

  const KEY k = m.run();
  if (k != K_QUIT)
  {         
    mode = 0x0;  
    firm = 0L;
    
    if (m.get_bool(F_COM))
      mode |= 0x1;
    if (m.get_bool(F_DITTA))
    {
      mode |= 0x2;
      firm = m.get_long(F_CODDITTA);
    }
    if (m.get_bool(F_CONFIG))
      mode |= 0x4;
    if (m.get_bool(F_770))
      mode |= 0x8;
    if (m.get_bool(F_CUSTOM))
      mode |= 0x10;
      
    floppy_path.format("%c:%c", toupper(m.get(F_FLOPPY)[0]), SLASH);
    floppy_path.add(m.get(F_PATH));
    
    desc = m.get(F_DESCR);
  }  
  
  return k;
} 

void TArchive_app::add_file(const TFilename& name)
{
  TAFile_info* fi = new TAFile_info;
  fi->_name = name;
  fi->_size = fsize(name);
  fi->_disk = fi->_last_disk = 1;
  _zip_list.add(fi);
}

bool TArchive_app::split_file(const TFilename& archive, unsigned long max_chunk)
{
  bool ok = true;
  TFilename output(archive);
  output.ext("z00");

  if (max_chunk > 0 && max_chunk < LONG_MAX) // Devo davvero suddividere archive?
  {
    int disk = 0;
    unsigned long scritti = 0;

    FILE* i = fopen(archive, "rb");
    if (i == NULL)
      return FALSE;
    
    FILE* o = fopen(output, "wb");
    
    const int BUFSIZE = 1024*16; 
    TString buf(BUFSIZE);
    char* buffer = buf.get_buffer();
    
    ok = true;
    while (ok)
    {
      const size_t letti = fread(buffer, 1, BUFSIZE, i);
      if (letti == 0)
        break;
      
      if (scritti >= max_chunk)
      {
        fclose(o);    
        add_file(output);
         
        TString4 ext; ext.format("z%02d", ++disk);
        output.ext(ext);
        o = fopen(output, "wb");
        scritti = 0;
      }

      ok = fwrite(buffer, letti, 1, o) > 0;
      scritti += letti;
    }    
    fclose(i);
    fclose(o);

    archive.fremove();
  }
  else
  {
    if (output.exist())
      output.fremove();
    ok = ::rename(archive, output) == 0; // Basta rinominarlo
  }
  
  if (ok)
    add_file(output);
  
  return ok;
}    

bool TArchive_app::zip_dir(const TFilename& name, unsigned long max_chunk)
{
  TFilename tmp; tmp.tempdir();
  tmp.add(name.name());
  tmp.ext("zip");

  bool ok = false;

  if (name.full())
  {
    TFilename filenames = name; filenames.add("*.*");
    TString msg;
    msg << TR("Creazione del file temporaneo ") << tmp << "...";
    TIndwin waitw(100, msg, false, false); 
	  ok = aga_zip(filenames, tmp);
  }
	
  if (ok && tmp.exist())
		ok = split_file(tmp, max_chunk);
          
  return ok;
}

bool TArchive_app::can_save_as(const TFilename& src, const TFilename& dst) const
{
  if (dst.exist())
    remove(dst);
  long s = fsize(src);
  return xvt_fsys_test_disk_free_space(dst.left(3), s) != 0;
}

bool TArchive_app::save_zip_files(const TFilename& floppy_path, const TString& desc, unsigned long max_chunk)
{
  // Assegna un disco di destinazione a tutti i files
  int disk = 1;
  unsigned long written = 0;
  int i;
  
  for (i = 0; i < _zip_list.items(); i++)
  {
    TAFile_info& fi = (TAFile_info&)_zip_list[i];
    if (written > 0 && (written+fi._size) >= max_chunk)
    {
      disk++;
      written = 0;
    }
    fi._disk = disk;
    written += fi._size;
  }
  
  bool ok = true;      

  const bool is_floppy = is_real_floppy(floppy_path);
  if (is_floppy)
  {
    const char* msg = TR("Si desidera procedere con l'operazione di salvataggio?");
    if (disk == 1)
      ok = yesno_box(FR("Controllare che il primo disco sia nel drive %c:\n%s"), floppy_path[0], msg);
    else
      ok = yesno_box(FR("Preparare %d dischetti vuoti e controllare che il primo sia nel drive %c:\n%s"), disk, floppy_path[0], msg);
  }  
  else
  {
    if (!xvt_fsys_mkdir(floppy_path))
      ok = error_box(FR("Impossibile creare la cartella %s"), (const char*)floppy_path);
  }
  
  if (ok)  
  {  
    const TDate oggi(TODAY);
    TFilename name; name = floppy_path; name.add("backup.ini");
    name.fremove();
    TConfig ini(name, "Main");
    ini.set("Date", oggi.string());
    ini.set("Description", desc);
    ini.set("Disks", disk);
    ini.set("Format", "zip");
    
    TFilename last_name;
    long size = 0;
    for (i = 0; i < _zip_list.items(); i++)
    {
      const TAFile_info& fi = (const TAFile_info&)_zip_list[i];
      name = fi._name.name(); name.ext("");
      if (name != last_name)
      {
        ini.set("FirstDisk", fi._disk, name);
        last_name = name;
        size = 0;
      }
      ini.set("LastDisk", fi._disk);
      ini.set("Size", size += fi._size);
    }  
    ini.set_paragraph("Main");  // Forza scrittura ultimo paragrafo
    
    int curr_disk = 1;   
    
    TProgind pi(_zip_list.items(), TR("Salvataggio dati in corso..."), FALSE, TRUE);
    for (i = 0; i < _zip_list.items(); i++)
    {
      pi.addstatus(1);
      const TAFile_info& fi = (const TAFile_info&)_zip_list[i];
      if (fi._disk != curr_disk)
        message_box(FR("Inserire il disco vuoto n.%d"), curr_disk = fi._disk);
      TFilename dest;
      dest = floppy_path; dest.add(fi._name.name());
      while(!can_save_as(fi._name, dest))  
      {
        if (!yesno_box(FR("Sul disco %d non c'e' spazio sufficiente per il file %s\nSi desidera sostituire il disco e ritentare?"), curr_disk,  (const char*)dest))
          return false;
      }
      fcopy(fi._name, dest);
      fi._name.fremove();
    }  
  }

  return ok;
}

void TArchive_app::backup(int mode, long firm, const TFilename& floppy_path, const TString& desc)
{
  TPointer_array ditte;
  if (firm == 0)
    prefix().firms(ditte);
  else
    ditte.add_long(firm);

  const TString16 old = prefix().name();
  prefix().set(NULL);   // Libera tutti i lock
  
  long max_chunk = LONG_MAX;

  if (is_real_floppy(floppy_path))
  {
    message_box(FR("Inserire un disco vuoto nel drive %c:"), floppy_path[0]);
    max_chunk = xvt_fsys_get_disk_size(floppy_path, 'B') - (1024L*64L);
  }
 
  _zip_list.destroy();                                            
                                              
  bool ok = true;
  
  TFilename name;
  if (ok && (mode & 0x1))
  {
    name = firm2dir(0);
    ok = zip_dir(name, max_chunk);
  }
  
  if (ok && (mode & 0x2))
  {
    for (int i = 0; ok && i < ditte.items(); i++)
    {                    
      firm = ditte.get_long(i);
      if (prefix().exist(firm))
      {
        name = firm2dir(firm);
        ok = zip_dir(name, max_chunk);
      }
    }
  }
  
  if (ok && (mode & 0x4))
  {
    name = firm2dir(-1);     // __ptprf
    name.add("config");      // Aggiungi configurazioni
    ok = zip_dir(name, max_chunk);
  }  
  if (ok && (mode & 0x8))
  {
    name = firm2dir(-1);     // __ptprf
    name.add("m770");        // Aggiungi 770
    if (name.exist())
      ok = zip_dir(name, max_chunk);
  }
  if (ok && (mode & 0x10))
  {
    name = firm2dir(-1);     // __ptprf
    name.add("custom");      // Aggiungi personalizzazioni
    if (name.exist())
      ok = zip_dir(name, max_chunk);
  }

  prefix().set(old);         // Ripristina prefix
  
  if (ok)
    save_zip_files(floppy_path, desc, max_chunk);
  else
    error_box(TR("Si � verificato un errore di accesso al disco:\nVerificare lo spazio disponibile"));
}

bool TArchive_app::read_paragraph(TConfig& ini, const char* para)
{
  const int first = ini.get_int("FirstDisk", para);
  const int last = ini.get_int("LastDisk");
  const bool ok = first > 0 && last >= first;
  if (ok)
  {
    TFilename dir; dir.tempdir();
    TString16 ext;
    int e = 0;
    for (int d = first; d <= last; d++)
    {
      TAFile_info* fi = new TAFile_info;
      TFilename& name = fi->_name;
      ext.format("z%02d", e++);
      name = dir; name.add(para); name.ext(ext);
      fi->_disk = d;
      fi->_last_disk = last;
      fi->_size = ini.get_int("Size");
      _zip_list.add(fi);
    }
  }
  return ok;
}

void TArchive_app::load_zip_files(const TFilename& floppy_path)
{   
  TString msg;
  TFilename dir; dir.tempdir();
  const int files = _zip_list.items(); 
  TProgind pi(files, TR("Ripristino dati in corso..."), TRUE, TRUE);
  int curr_disk = 1;          
  for (int i = 0; i < files; i++)
  {            
    const TAFile_info& info = (const TAFile_info&)_zip_list[i];
    const int& disk = info._disk;

    msg = TR("Ripristino "); msg << info._name.name() << TR("...");
    pi.set_text(msg);
    pi.addstatus(1);
    if (pi.iscancelled())
      break;
    
    if (is_real_floppy(floppy_path))
    {
      if (disk != curr_disk)
      {
        message_box(FR("Inserire il disco %d del backup"), disk);
        curr_disk = disk;
      }
    }
  
    TFilename src; src = floppy_path; src.add(info._name.name());
    TFilename dst; dst = dir; dst.add(info._name.name()); dst.ext("zip");
    while (!fcopy(src, dst, i > 0))
      if (!yesno_box(TR("Si desidera ritentare?")))
        return;   
    if (disk == info._last_disk)
    {
      TFilename exdir;
      exdir = firm2dir(-1); exdir.add(dst.name()); exdir.ext("");
			aga_unzip(dst, exdir);
    }
  }
}

void TArchive_app::restore(int mode, long firm, const TFilename& floppy_path)
{
  _zip_list.destroy();
  
  TFilename name; name = floppy_path; name.add("backup.ini");
  TConfig ini(name, "Main");
  ini.write_protect();
  
  if (mode & 0x1)
    read_paragraph(ini, "com");
  
  if (mode & 0x2)
  {
    if (firm > 0)
    {
      TString8 para; para.format("%05lda", firm);
      read_paragraph(ini, para);
    }
    else
    {
      TString_array para;
      ini.list_paragraphs(para);
      for (int i = 0; i < para.items(); i++)
      {
        const TString& n = para.row(i);
        if (atol(n) > 0 && n.right(1) == "a")
          read_paragraph(ini, n);
      }
    }
  }

  if (mode & 0x4)
    read_paragraph(ini, "config");

  if (mode & 0x8)
    read_paragraph(ini, "m770");

  if (mode & 0x10)
    read_paragraph(ini, "custom");

  const int tot = _zip_list.items();
  if (tot > 0)
  {
    TString fola;
    fola = TR("Verranno ripristinati i seguenti archivi:\n");
    for (int i = 0; i < tot; i++)
    {
      const TAFile_info& info = (const TAFile_info&)_zip_list[i];
      if (info._disk == info._last_disk)
      {
        TFilename n = info._name.name();
        n.ext("");
        fola << n << ", ";
      }
    }
    fola.rtrim(2);
    fola << TR(".\nSi desidera continuare?");
    if (yesno_box(fola))
    {
      const TString16 old = prefix().name();
      prefix().set(NULL);     // Libera tutti i lock
      load_zip_files(floppy_path); // Scompatta i files  
      prefix().set(old);      // Ripristina prefix
    }
  }
  else
    warning_box(TR("I dati richiesti non sono presenti sul backup"));
}

void TArchive_app::interactive_mode()
{
  KEY k;   
  int mode;
  long firm;
  TFilename floppy_path;
  TString80 desc;
  
  while ((k = query(mode, firm, floppy_path, desc)) != K_QUIT)
  {
    if (mode)
    {
      if (k == K_SAVE) 
      {     
        backup(mode, firm, floppy_path, desc);
      }
      else
      {                   
        TFilename ini_name = floppy_path; ini_name.add("backup.ini");      
        if (is_real_floppy(floppy_path))
          message_box(FR("Inserire il primo disco del backup nel drive %c"), floppy_path[0]);
        if (ini_name.exist())
    		  restore(mode, firm, floppy_path);
        else
          error_box(FR("Impossibile trovare il file %s"), (const char*)ini_name);
      }
    }
    else
      error_box(TR("Non e' stato specificato nessun archivio"));  
  }
}

void TArchive_app::batch_mode(const TString& cmd)
{
  const int mode = ~0;  
  const long firm = 0;
  const TFilename floppy_path = cmd;
  restore(mode, firm, floppy_path);
}

void TArchive_app::main_loop()
{     
  TString cmd;
  
  if (argc() > 2)
  {
    TFilename ini = argv(2);
    if (ini.compare("/i", 2, TRUE) == 0) // qui
    {
      ini.ltrim(2);
      TConfig cfg(ini, "Main");
      cmd = cfg.get("Action");
      if (cmd.compare("Restore ", 8, TRUE) == 0) //qui
      {
        cmd.ltrim(8);
        cmd.trim();
      }
      else
        cmd.cut(0);
    }
    else
    {
      if (ini.exist())
        cmd = ini;
    }
  }
  
  if (cmd.empty())
  {
    TMailbox mail;
    TMessage* msg = mail.next_s("RESTORE");
    if (msg == NULL)
    {
      mail.restart();
      msg = mail.next_s("Restore");
    }  
    if (msg)
      cmd = msg->body();
  }
  
  if (cmd.empty())
    interactive_mode();
  else
    batch_mode(cmd);
}

int ba2200(int argc, char* argv[])
{ 
  TArchive_app a;
  a.run(argc, argv, TR("Archiviazione"));                  
  return 0;
}