#include <conio.h>
#include <direct.h>  
#include <stdlib.h>  
#include <time.h>
#include <xvt.h>

#include <applicat.h>
#include <assoc.h>
#include <golem.h> 
#include <mailbox.h>
#include <msksheet.h>
#include <prefix.h>
#include <progind.h>
#include <relation.h>
#include <urldefid.h>
#include <utility.h>
#include <xvtility.h>

#include <anagiu.h>
#include <clifo.h>
#include <nditte.h>

#include "bafax.h"

#define STRICT   
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <shellapi.h>
#include "faxdll.h"

///////////////////////////////////////////////////////////
// Finalmente un programma multimediale!
///////////////////////////////////////////////////////////

const word SPEAKER_PORT      = 0x61;
const word PIT_CONTROL       = 0x43;
const word PIT_CHANNEL_2     = 0x42;
const unsigned long PIT_FREQ = 0x1234DD;

void sound(word frequency)
{
  const word counter = word(PIT_FREQ / frequency);
  _outp(PIT_CONTROL, 0xB6);
  _outp(PIT_CHANNEL_2, counter & 0xFF);
  _outp(PIT_CHANNEL_2, counter >> 8);

  // Connect the speaker to the PIT
  _outp(SPEAKER_PORT, _inp(SPEAKER_PORT) | 3);
}

void nosound()
{ 
  // Sconnect the speaker from the PIT
  _outp(SPEAKER_PORT, _inp(SPEAKER_PORT) & 0xFC);
}

///////////////////////////////////////////////////////////
// Dati destinatario
///////////////////////////////////////////////////////////

class TFax_data : public TAssoc_array
{           
public:
  void set(const char* key, const char* value);
  const char* get(const char* key) const;
  
  void set_file(const char* file) { set("FILELIST", file); }
  char* get_char_star(const char* name) const;
  
  TFax_data& operator=(const TAssoc_array& a) { copy(a); return *this; }
  TFax_data& operator=(const TFax_data& d) { copy(d); return *this; }
  
  TFax_data();  
  TFax_data(const TFax_data& d) { copy(d); }
  virtual ~TFax_data();
};

TFax_data::TFax_data()
{          
}

TFax_data::~TFax_data()
{          
}

void TFax_data::set(const char* key, const char* value)
{
  add(key, TString(value), TRUE);
}

const char* TFax_data::get(const char* key) const
{
  const TString* s = (const TString*)((TAssoc_array*)this)->objptr(key);
  return s ? (const char*)*s : "";
}


char* TFax_data::get_char_star(const char* name) const
{     
  if (stricmp(name, "FULLDESTFAX") == 0)
  {
    TString num(24);
    num << get("DIALING");
    num << get("PREFIX");
    num << get("DESTFAX");
    ((TFax_data*)this)->set(name, num);
  }

  const TString* s = (const TString*)((TAssoc_array*)this)->objptr(name);
  return  s ? (char*)(const char*)*s : "";
}

///////////////////////////////////////////////////////////
// Coda fax
///////////////////////////////////////////////////////////
           
class TFax_queue : private TArray
{ 
  TFax_data _default;
  
public:
  int push(TFax_data* data); 
  TFax_data& peek_last();
  const TFax_data& peek_last() const;
  const TFax_data& peek_default();
  
  TFax_data& pop(); 
  
  bool empty() const { return items() == 0; } 
  
  void on_firm_change();
  
  TFax_queue();
  virtual ~TFax_queue();
};

TFax_queue::TFax_queue()
{
  TConfig ini(CONFIG_USER, "Fax");
  _default = ini.list_variables();
}

TFax_queue::~TFax_queue()
{
  TConfig ini(CONFIG_USER, "Fax");
  _default.restart();          
  for (THash_object* obj = _default.get_hashobj(); obj; obj = _default.get_hashobj())
  {                     
    const TString& name  = obj->key();
    const TString& value = (const TString&)obj->obj();
    ini.set(name, value);
  }
}

TFax_data& TFax_queue::peek_last()
{
  const int l = last();
  return l >= 0 ? (TFax_data&)*objptr(l) : _default;  
}

const TFax_data& TFax_queue::peek_last() const
{
  const int l = last();
  return l >= 0 ? (TFax_data&)*objptr(l) : _default;  
}

const TFax_data& TFax_queue::peek_default()
{
  on_firm_change();
  return _default;
}

TFax_data& TFax_queue::pop()
{
  TFax_data* d = (TFax_data*)remove(0, TRUE);
  if (d != NULL)
  {
    _default = *d;
    delete d;
  }  
  return _default;  
}

int TFax_queue::push(TFax_data* data)
{
  return add(data, -1);
}

void TFax_queue::on_firm_change()
{                        
  static long old_firm = 0;

  TPrefix& pref = prefix();
  pref.set("DEF");
  const long firm = pref.get_codditta();
  
  if (firm != old_firm)
  {
    TLocalisamfile ditte(LF_NDITTE);
    ditte.put(NDT_CODDITTA, firm);
    if (ditte.read() == NOERR)
    {             
      old_firm = firm;
    
      TString tmp(80);
      
      tmp = ditte.get(NDT_PFAX);
      if (tmp.not_empty()) tmp << ',';
      tmp << ditte.get(NDT_FAX);
      if (tmp.not_empty())
        _default.set("FROMFAX", tmp);
      
      tmp = ditte.get(NDT_PERRIF);
      if (tmp.not_empty())
        _default.set("FROMNAME", tmp); 
        
      tmp = ditte.get(NDT_RAGSOC);
      if (tmp.not_empty())
        _default.set("FROMFIRM", tmp);
    }
  }  
}

///////////////////////////////////////////////////////////
// TFax_list
///////////////////////////////////////////////////////////

class TFax_list : public TToken_string
{ 
public:
  bool is_faxable(const TFilename& name) const;

  TFax_list();
  TFax_list(const char* f); 
  virtual ~TFax_list() { }
};

TFax_list::TFax_list()
         : TToken_string(80, '+')
{ }

TFax_list::TFax_list(const char* name)
         : TToken_string(name, '+')
{ }        

bool TFax_list::is_faxable(const TFilename& name) const
{
  // Lista delle estensioni accettabili
  const char* known_ext[] = { "BMP", "FMF", "PCX", "PMF", "TIF", "TXT", NULL };
  
  // Estensione del file da controllare
  const TString16 ext = name.ext();
  
  // Se il file ha un'estensione riconosciuta lo aggiunge alla lista
  for (int e = 0; known_ext[e]; e++)
  {
    if (ext.compare(known_ext[e], -1, TRUE) == 0)
      return TRUE;
  }    
  
  return FALSE;
}

///////////////////////////////////////////////////////////
// TDDE_fax
///////////////////////////////////////////////////////////

class TLog_mask;       
class TFax_mask;

class TDDE_fax : public TDDE
{ 
  PAPPINFO _pappinfo;
  SEND_FAX _send_fax;
  
  TBit_array _port;
  TString_array _pending, _sending, _failed, _complete;
  TFax_queue _queue;
  
  TLog_mask* _log_mask; 
  TFax_mask* _fax_mask;

  bool _close_when_idle;

protected:
  TString_array* log2array(WORD log);
  const char* error2string(FAXERROR e) const;
  void add_log(WORD log, PSEND_FAX sf);
  void remove_log(WORD log, PSEND_FAX sf);
  void show_status(PSEND_FAX sf);
  void update_all_logs();
  void update_log(WORD log);
  
  bool send_next_fax(); 
  void push_fax(const char* fax);
 
  TString& ragsoc2name(TString& ragsoc) const;
  bool ricerca_clifo(char cf, const TString& codice, TFax_data& data) const;
  bool ricerca_persone(char fg, const TString& codice, TFax_data& data) const;
  bool ricerca_ditte(const TString& codice, TFax_data& data) const;
  
  void choose_temp_name(TFilename& name) const;
  bool exec_command(const TString& comm, TToken_string& args);
  void handle_fax_message(word wparam, long lparam);
  void handle_drop_files(word wparam);

  // @cmember Ritorna TRUE se il server non e' in attesa di nessun lavoro
  bool can_close() const;

public: // TObject
  virtual bool ok() const { return _pappinfo != NULL; }

public: // TDDE
  virtual const char* get_app_name() const { return "EASYFAX"; }
  virtual const char* get_topics() const { return "FAX"; }

  virtual bool do_initiate(word id, const TString& topic);
  virtual bool do_custom_message(word msg, word wparam, long lparam);
  virtual bool do_execute(word id, const TString& cmd);

public:  
  bool run_mask();

  void log_delete(WORD log, unsigned long id);
  bool resend_fax(unsigned long id);
  
  void on_firm_change();

  bool set_destination(const TString& tipo, const TString& codice, TFax_data& data) const;
  bool push_destination(const TString& tipo, const TString& codice);
  
  void auto_configure();
  
  TDDE_fax();
  virtual ~TDDE_fax();
};

///////////////////////////////////////////////////////////
// TFax_mask
///////////////////////////////////////////////////////////

class TFax_mask : public TMask
{        
  TDDE_fax* _fax;
  TAssoc_array _table;
 
protected:
  TMask_field* lookup(const char* field);

  static bool code_handler(TMask_field& f, KEY k);
  static bool button_handler(TMask_field& f, KEY k);

public:
  void set_field(const char* field, const char* value);
  const TString& get_field(const char* field);
  
  void set_data(const TFax_data& data);
  void get_data(TFax_data& data) const;
  
  TFax_mask(TDDE_fax* fax);
  virtual ~TFax_mask();
};

TFax_mask::TFax_mask(TDDE_fax* fax) 
         : TMask("bafax01"), _fax(fax)
{       
  const char* pippe[] = { "DESTFAX", "FROMFAX", "PREFIX", NULL };

  for (int i = fields()-1; i >= 0; i--)
  {
    TMask_field& f = fld(i);
    if (f.in_group(1))
    {
      f.set_handler(code_handler);
      const TFieldref* c = f.field();
      if (c != NULL)
      {
        for (int p = 0; pippe[p] != NULL; p++)
          if (c->name() == pippe[p])
            f.allow_pipe();
      }  
    }  
    else
      if (f.in_group(2))
        f.set_handler(button_handler);
  }   
}

TFax_mask::~TFax_mask()
{ }

TMask_field* TFax_mask::lookup(const char* field)
{
  TString* id = (TString*)_table.objptr(field);
  if (id == NULL)
  {
    for (int i = fields()-1; i >= 0; i--)
    {
      TMask_field& f = fld(i);
      const TFieldref* c = f.field();
      if (c && c->name() == field)
        break;
    }
    id = new TString;
    id->format("%d", i);
    _table.add(field, id);
  }
  const int pos = atoi(*id);
  return pos < 0 ? NULL : &fld(pos);
}


void TFax_mask::set_field(const char* field, const char* value)
{
  TMask_field* f = lookup(field);
  if (f) f->set(value);
}

const TString& TFax_mask::get_field(const char* field)
{
  TMask_field* f = lookup(field);
  return f ? f->get() : "";
}

bool TFax_mask::code_handler(TMask_field& f, KEY k)
{   
  bool ok = TRUE;
  if (k == K_TAB && f.focusdirty())
  {
    TFax_mask& m = (TFax_mask&)f.mask();
    const TString tipo = m.get_field("TIPO");
    const TString codice = f.get();
    TFax_data data;
    ok = m._fax->set_destination(tipo, codice, data);
    if (ok)
      m.set_data(data);
  }                                                                     
  return ok;
} 

bool TFax_mask::button_handler(TMask_field& f, KEY k)
{
  if (k == K_SPACE)
  {        
    TFax_mask& m = (TFax_mask&)f.mask();
    TMask_field* fax = m.lookup("DESTFAX");
    CHECK(fax, "Can't find fax number field");
    
    TString num = fax->get();
    const TString& prompt = f.prompt();
    if (prompt != "#")
    {
      if ((word)num.len() < fax->size())
        num << prompt;
    }    
    else  
      num.rtrim(1);
    fax->set(num);
    
    const TMask_field* dialmode = m.lookup("DIALING");
    CHECK(dialmode, "Can't find dial mode field");
    const bool tone = dialmode->get()[0] == 'T';
    
    const char* keys[] = {  "1",  "2",  "3", 
                            "4",  "5",  "6", 
                            "7",  "8",  "9", 
                            ",",  "0",  "#" };
    const word freqs[] = { 1230, 1320, 1460, 
                           1235, 1325, 1465, 
                           1240, 1330, 1470, 
                           1245, 1335, 1475 };
    for (int i = 0; i < 12; i++) if (prompt == keys[i])
    {       
      if (tone)
      {
        sound(freqs[i]);      
        const clock_t end = clock() + CLOCKS_PER_SEC / 5;
        while (clock() < end);
        nosound();
      }
      else
      {             
        if (i >= 9 && i <= 11)
          i = i == 10 ? 9 : 0;
        for (int t = 0; t <= i; t++)
        {
          sound(50);      
          clock_t end = clock();
          while (clock() == end);
          nosound();
          end = clock();
          while (clock() == end);
        }
      }  
      break;
    }
  }
  return TRUE;
}

void TFax_mask::set_data(const TFax_data& ass)
{      
  TAssoc_array& data = (TAssoc_array&)ass;
  data.restart();
  for (THash_object* obj = data.get_hashobj(); obj; obj = data.get_hashobj())
  {
    const TString& name = obj->key();
    const TString& value = (const TString&)obj->obj();
    set_field(name, value);
  }
}

void TFax_mask::get_data(TFax_data& data) const
{
  for (int i = fields()-1; i >= 0; i--)
  {                 
    const TMask_field& f = fld(i);
    const TFieldref* fr = f.field();
    if (fr)
      data.set(fr->name(), f.get());
  }
}


///////////////////////////////////////////////////////////
// TQueues_mask
///////////////////////////////////////////////////////////

class TLog_mask : public TMask
{          
  TDDE_fax* _fax;

protected:
  void update_sheet(short id, const TString_array& a);
  
  WORD sheet2log(const TSheet_field& s) const;
  
  void set_notify(short id);
  static bool log_notify(TSheet_field& f, int row, KEY k);
  static bool delrec_handler(TMask_field& f, KEY k);
  static bool fax_handler(TMask_field& f, KEY k);

public:
  void update_log(WORD log, const TString_array& a);

  TLog_mask(TDDE_fax* fax);
  virtual ~TLog_mask() { }
};

TLog_mask::TLog_mask(TDDE_fax* fax) 
         : TMask("bafax02"), _fax(fax)
{   
  set_notify(F_PENDING);
  set_notify(F_SENDING);
  set_notify(F_FAILED);
  set_notify(F_COMPLETE);
}

WORD TLog_mask::sheet2log(const TSheet_field& s) const
{
  WORD log;
  switch (s.dlg())
  {
    case F_PENDING:  log = SUBSCRIBE_LOG_PENDING;  break;
    case F_SENDING:  log = SUBSCRIBE_LOG_SENDING;  break;
    case F_COMPLETE: log = SUBSCRIBE_LOG_COMPLETE; break;
    case F_FAILED:   log = SUBSCRIBE_LOG_FAILED;   break;
    default:         log = 0; break;
  }
  return log;
}

bool TLog_mask::log_notify(TSheet_field& s, int, KEY k)
{            
  if (k == K_INS)
    return FALSE;
/*
  if (k == K_INS)
  {   
    const int last = s.items()-1;
    if (last >= 0 && yesno_box("Si conferma l'eliminazione dei fax?"))
    {
      TLog_mask& m = (TLog_mask&)s.mask();
      const WORD log = m.sheet2log(s);
      for (int i = last; i >= 0; i--)
      {
        const char* strid = s.row(i).get(0);
        unsigned long id; sscanf(strid, "%lu", &id);
        m._fax->log_delete(log, id);
        do_events();
      }
    }  
    return FALSE;
  }
*/
  return TRUE;  
} 

bool TLog_mask::delrec_handler(TMask_field& f, KEY k)
{
  if (k == K_SPACE)
  {
    TMask& m = f.mask();

    const char* strid = m.get(101);
    if (yesno_box("Si conferma l'eliminazione del fax %s?", strid))
    {
      TSheet_field& s = *m.get_sheet();
      TLog_mask& l = (TLog_mask&)s.mask();
      const WORD log = l.sheet2log(s);
      CHECK(log, "Invalid Fax log");
      unsigned long id; sscanf(strid, "%lu", &id);
      l._fax->log_delete(log, id);
    }  
  }
  return TRUE;
}

bool TLog_mask::fax_handler(TMask_field& f, KEY k)
{
  if (k == K_SPACE)
  {           
    TMask& m = f.mask();
    TSheet_field& s = *m.get_sheet();
    TLog_mask& l = (TLog_mask&)s.mask();
    const char* strid = m.get(101);
    if (f.yesno_box("Si desidera rispedire il fax %s?", strid))
    {
      unsigned long id; sscanf(strid, "%lu", &id);
      l._fax->resend_fax(id);
    }  
  }
  return TRUE;
}


// @cmember Setta gli handlers dello sheet <p id> e del tasto elimina della sua maschera
void TLog_mask::set_notify(short id)
{
  TMask_field& f = field(id);
  CHECKD(f.is_kind_of(CLASS_SHEET_FIELD), "Not a sheet ", id);
  TSheet_field& s = (TSheet_field&)f;
  s.set_notify(log_notify);                
  
  TMask& sm = s.sheet_mask();
  sm.set_handler(DLG_DELREC, delrec_handler);
  if (id == F_FAILED) 
    sm.set_handler(100, fax_handler);
}

// @cmember Ricopia l'array <p a> nello sheet <p id>
void TLog_mask::update_sheet(short id, const TString_array& a)
{
  TSheet_field& s = (TSheet_field&)field(id);
  s.rows_array() = a;
  s.force_update();
}

void TLog_mask::update_log(WORD log, const TString_array& a)
{    
  // Convere il codice della coda nell'identificatore dello sheet
  short id;
  switch(log)
  {
    case SUBSCRIBE_LOG_PENDING:  id = F_PENDING;  break;
    case SUBSCRIBE_LOG_SENDING:  id = F_SENDING;  break;
    case SUBSCRIBE_LOG_COMPLETE: id = F_COMPLETE; break;
    case SUBSCRIBE_LOG_FAILED:   id = F_FAILED;   break;
    default: id = DLG_NULL; break;
  } 
  
  // Aggiorna lo sheet, se l'ha trovato
  if (id != DLG_NULL)
    update_sheet(id, a);
}

///////////////////////////////////////////////////////////
// DDE Fax server
///////////////////////////////////////////////////////////


TDDE_fax::TDDE_fax() 
        : _pappinfo(NULL), _log_mask(NULL), _fax_mask(NULL),
          _close_when_idle(FALSE)
{               
  _pappinfo = FaxRegisterApp(get_app_name(), hwnd());
  if (_pappinfo)
  {    
    start_server();                             // Inizia DDE server
    FaxSubscribe(_pappinfo, SUBSCRIBE_LOG_ALL); // Connettiti a Faxman
    update_all_logs();                          // Aggiorna tutte le code
    DragAcceptFiles((HWND)hwnd(), TRUE);        // Attiva Drag'n'drop
  }  
  else
  {
    xvt_statbar_set("Impossibile connettersi a FAXMAN");
    beep();
  }  
}

TDDE_fax::~TDDE_fax()
{ 
  if (_pappinfo)
  {
    DragAcceptFiles((HWND)hwnd(), FALSE);   // Non accettare piu' Drag'n'drop

    FaxUnregisterApp(_pappinfo);            // Sconnettiti da Faxman
    _pappinfo = NULL;
  }  

  if (_log_mask)
  {
    delete _log_mask;
    _log_mask = NULL;
  }
}

bool TDDE_fax::do_initiate(word id, const TString& topic)
{
  return TRUE;                    
}

bool TDDE_fax::exec_command(const TString& cmd, TToken_string& arg)
{
  if (cmd.compare("FileClose", -1, TRUE) == 0)
  {
    if (can_close())
      main_app().stop_run();
    else
      _close_when_idle = TRUE;  
  }
  
  if (cmd.compare("SetRecipient", -1, TRUE) == 0)
  {
    TString tipo = arg.get(0); tipo.trim();
    TString codice = arg.get(); codice.trim();
    push_destination(tipo, codice);
  }

  return TRUE;
}

bool TDDE_fax::do_execute(word id, const TString& cmd)
{           
  bool ok = TRUE;

  TToken_string commands(cmd, ']');
  TToken_string arguments(80, ',');
  
  for (TString command = commands.get(0); command.not_empty(); command = commands.get())
  {
    if (command[0] == '[')
      command.ltrim(1);

    const int bracket = command.find('(');
    if (bracket > 0)
    { 
      arguments = command.mid(bracket+1, -1);
      arguments.rtrim(1);
      arguments.trim();
      command.cut(bracket);
    }       
    else
      arguments.cut(0);
    
    command.trim();
    ok = exec_command(command, arguments);
    if (!ok) break;
  } 
  
  return ok;
}

// Genera il nome di un file temporaneo: se esiste gia' ripete il ciclo.
void TDDE_fax::choose_temp_name(TFilename& name) const
{   
  static long lastfax = 0;
   
  TFilename path;                     
  path.tempdir();                     // Directory temporanea
  path.add("FAX");                    // Aggiunge la directory FAX
  
  if (lastfax == 0 && !fexist(path))
    make_dir(path);
  
  do
  {                 
    TString16 f; f.format("FAX%05ld.FMF", ++lastfax);
    name = path; name.add(f);
  } while (fexist(name));
} 

void TDDE_fax::on_firm_change() 
{ 
  _queue.on_firm_change(); 
}

void TDDE_fax::handle_fax_message(word wparam, long lparam)
{ 
  switch(wparam)
  {           
  case FAXMODEMMSG:
    if (lparam)
    {
      PFAXDEVICE dev = (PFAXDEVICE)lparam;
      
      TString msg(24);
      msg = "E' stata ";
      if (!dev->wFlags)  
      {
        _port.reset(dev->nPort);       // Removing a port
        msg << "scollegata";
        beep();
      }  
      else
      {
        _port.set(dev->nPort);         // Adding a port
        msg << "collegata";
      }  
      msg << " la porta COM" << dev->nPort;
      xvt_statbar_set(msg);
    }
    break;
  case FAXGETFILENAME:
    if (lparam)
    {
      TFilename tmp; choose_temp_name(tmp);
      strcpy((char*)lparam, tmp);
    }
    break;
  case FAXPRINTMSG:
    if (lparam)
    {                      
      PPRINTSTAT ps = (PPRINTSTAT)lparam;
 
      if (ps->pStat == PRN_OK)
      {
        TFax_data& data = _queue.peek_last();
        data.set_file(ps->FileName);
        beep();
        if (_fax_mask == NULL)
          send_next_fax();
      }    

      GlobalFree((HGLOBAL)lparam);
    }
    break;
  case FAXLOGADD:
    if (lparam)
    {
      PSEND_FAX sf = (PSEND_FAX)lparam;
      add_log(sf->wLog, sf);
    }  
    break;  
  case FAXLOGREMOVE:
    if (lparam)
    {
      PSEND_FAX sf = (PSEND_FAX)lparam;
      remove_log(sf->wLog, sf);
    }  
    break;  
  case FAXSENDMSG:
    if (lparam)  
    {
      PSEND_FAX sf = (PSEND_FAX)lparam;
      show_status(sf);
    }
    break;
  default:
    break;
  }
}

// Gestione del Drag'n'drop
void TDDE_fax::handle_drop_files(word wparam)
{              
  TTemp_window tw(TASK_WIN);
  tw.maximize();
  
  xvt_statbar_set("Creazione fax...");
  do_events();

  HDROP hdrop = (HDROP)wparam;
  const int num_files = DragQueryFile(hdrop, -1, NULL, 0); // Numero totale di files

  TFilename fname;    // File corrente
  
  TFax_list file_list;
  for (int i = 0; i < num_files; i++)
  { 
    DragQueryFile(hdrop, i, (char*)(const char*)fname, fname.size());
    if (file_list.is_faxable(fname))
      file_list.add(fname);
  }
  DragFinish(hdrop);
  
  // Spedisce un unico fax con tutti i files droppati
  if (file_list.items() > 0)
  {              
    begin_wait();
    TFilename fax; choose_temp_name(fax);
    const word err = FaxCreate(_pappinfo, file_list, fax, 0L);
    xvt_statbar_set("");
    end_wait();
    if (err == 0)
      push_fax(fax);
    else
      error_box("Impossibile convertire il file %s", (const char*)file_list.get(err-1));
  }  
  else
    xvt_statbar_set("");
}                                

bool TDDE_fax::do_custom_message(word msg, word wparam, long lparam)
{          
  bool ok = TRUE;
  switch(msg)
  {       
  case WM_DROPFILES:
    handle_drop_files(wparam);
    break;              
  case WM_FAXMSG: 
    handle_fax_message(wparam, lparam); 
    break;
  default: 
    ok = FALSE;
    break;
  }
  return ok;
}             

bool TDDE_fax::resend_fax(unsigned long id)
{                                    
  PSEND_FAX sf;
  bool ok = FaxLogFind(_pappinfo, &sf, id) == 1;
  if (ok)
  {          
    const TFilename old_name(sf->szFileList);
    TFilename new_name(old_name); new_name.ext("FMP");
    rename(old_name, new_name);             // Rinomina file
    FaxLogDelete(_pappinfo, id, sf->wLog);  // Cancella fax
    do_events();                            
    rename(new_name, old_name);             // Ripristina file
    FaxSchedule(_pappinfo, sf);
    GlobalFreePtr(sf);
  }  
  return ok;  
}

bool TDDE_fax::send_next_fax()
{                
  static bool no_fax_error = FALSE;

  TTemp_window tw(TASK_WIN);

#if XVT_OS == XVT_OS_WIN  
  HWND hwnd = (HWND)xvt_vobj_get_attr(TASK_WIN, ATTR_NATIVE_WINDOW);
  SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
#endif  

  tw.maximize();
  
  if (_port.ones() == 0 && !no_fax_error)
  {
    no_fax_error = TRUE;
    error_box("Non c'e nessun Modem/Fax collegato");
  }

  CHECK(_fax_mask == NULL, "Double fax mask");
  _fax_mask = new TFax_mask(this);

  bool sent = FALSE;
  do
  {
    TFax_data& data = _queue.pop();
    _fax_mask->set_data(data);
    
    if (_fax_mask->run() == K_ENTER)
    {
      _fax_mask->get_data(data);
  
      PSEND_FAX sf = &_send_fax; 
      FaxInitSendStruct(_pappinfo, sf);
      
      sf->szDestFax     = data.get_char_star("FULLDESTFAX");
      sf->szDestName    = data.get_char_star("DESTNAME");
      sf->szToCompany   = data.get_char_star("DESTFIRM");
      
      sf->szFromFax     = data.get_char_star("FROMFAX");
      sf->szFromLine    = data.get_char_star("FROMNAME");
      sf->szFromCompany = data.get_char_star("FROMFIRM");
   
      sf->szFileList    = data.get_char_star("FILELIST");
      
      sf->szBanner = "%m %s| |Pagina %c di %p";
      sf->szCover = "cover1.pg";
      
      const DWORD id = FaxSchedule(_pappinfo, sf);
      sent = id > 0;
    }        
  } while (!_queue.empty());
  
  delete _fax_mask;
  _fax_mask = NULL;

#if XVT_OS == XVT_OS_WIN
  SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
#endif
  
  // Non iconizzare se ho aperta la finestra di stato
  if (_log_mask == NULL)
    tw.iconize();
  
  if (_close_when_idle && can_close())
    main_app().stop_run();
  
  return sent;
}

void TDDE_fax::push_fax(const char* fax)
{                                                 
  TFax_data* data = new TFax_data(_queue.peek_default());
  data->set_file(fax);
  _queue.push(data);
  
  if (_fax_mask == NULL)
    send_next_fax();
}

TString_array* TDDE_fax::log2array(WORD log)
{
  TString_array* array;
  switch(log)
  {
    case SUBSCRIBE_LOG_PENDING : array = &_pending; break;
    case SUBSCRIBE_LOG_SENDING : array = &_sending; break;
    case SUBSCRIBE_LOG_COMPLETE: array = &_complete; break;
    case SUBSCRIBE_LOG_FAILED  : array = &_failed; break;
    default: array = NULL;
  }
  return array;
}

const char* TDDE_fax::error2string(FAXERROR e) const
{
  static const char* msg[] = 
  {
    "",                                    // FAXERR_OK
    "Comando non ricosciuto (nessun OK)",  // FAXERR_ACK
    "Modem/Fax non riconosciuto",          // FAXERR_BADFAXMODEM
    "Errore di inizializzazione",          // FAXERR_INIT
    "Errore nei parametri FDIS",           // FAXERR_FDIS
    "Errore nell'identificatore locale",   // FAXERR_FLID
    "Errore di composizione del numero",   // FAXERR_DIAL
    "Errore di connessione al fax remoto", // FAXERR_FCON_ERR
    "Errore nella stringa FCSI",           // FAXERR_FCSI
    "Errore di ricezione FDIS",            // FAXERR_NEG_FDIS
    "Errore di stato errato",              // FAXERR_BADSTATE
    "Linea occupata",                      // FAXERR_BUSY
    "Segnale di libero non rilevato",      // FAXERR_NODIALTONE
    "Messaggio CONNECT non ricevuto",      // FAXERR_NOCONNECT
    "Interrotto dall'utente",              // FAXERR_CANCEL
    "FPTS nullo o errato",                 // FAXERR_FPTS
    "FHNG nullo o errato",                 // FAXERR_FHNG
    "FDCS nullo o errato",                 // FAXERR_FDCS
    "Errore di trasmissione",              // FAXERR_ERROR
    "File in formato errato",              // FAXERR_FILE
    "Fax Server incompatibile",            // FAXERR_VERSION
    "Tempo di trasmissione scaduto",       // FAXERR_TIMEOUT
    "Nessuna risposta a MPS",              // FAXERR_NO_MPS_RESP
    "Nessuna risposta a EOP",              // FAXERR_NO_EOP_RESP
    "Errore di connessione",               // FAXERR_NOTRAIN
  };
                
  if (e < FAXERR_OK || e > FAXERR_NOTRAIN)
    e = FAXERR_ERROR;
  
  return msg[e];
}

void TDDE_fax::add_log(WORD log, PSEND_FAX sf)
{
  TString_array* arr = log2array(log);
  if (arr)
  {
    TToken_string* l = new TToken_string(128);
    
    l->format("%lu", sf->dwID);                              // ID  
    
    l->add(sf->szDestFax);                                   // Dest. fax 
    
    TString destination(128);
    destination = sf->szDestName;
    destination.trim();
    if (*sf->szToCompany)
    { 
      if (destination.not_empty())
        destination << ", ";
      destination << sf->szToCompany;
    }  
    l->add(destination);                                     // Dest. name
    
    l->add(TDate(sf->nDay, sf->nMonth, sf->nYear).string()); // Date and time
    char ora[16]; sprintf(ora, "%02d:%02d:%02d", sf->nHour, sf->nMin, sf->nSecond);
    l->add(ora);
    
    l->add(sf->szFileList);                                  // Files
    
    l->add(error2string(sf->fe));
  
    arr->add(l);        // Aggiorna array del log
    if (_log_mask)      // Se aperta, aggiorna anche maschera
      _log_mask->update_log(log, *arr);
  }    
}

void TDDE_fax::remove_log(WORD log, PSEND_FAX sf)
{
  TString_array* arr = log2array(log);
  if (arr)
  {
    TString16 id; id.format("%lu", sf->dwID);
    for (int i = arr->items()-1; i >= 0; i--)
    {
      TToken_string& r = arr->row(i);
      if (id == r.get(0)) break;
    }
    if (i >= 0)
    {
      arr->destroy(i, TRUE);
      if (_log_mask)
        _log_mask->update_log(log, *arr);
    }    
  }  
}

void TDDE_fax::update_all_logs()
{      
  SEND_FAX  tmpsf;
  
  _pending.destroy();
  _sending.destroy();
  _failed.destroy();
  _complete.destroy();
  
  if (FaxLogInit(_pappinfo, SUBSCRIBE_LOG_PENDING) < MAXITERATORS) 
  {
    while (FaxLogNext(_pappinfo, &tmpsf) == LOGERR_CONTINUEIT)
      do_custom_message(WM_FAXMSG, FAXLOGADD, (LPARAM)&tmpsf);
  }
  
  if (FaxLogInit(_pappinfo, SUBSCRIBE_LOG_SENDING) < MAXITERATORS) 
  {
    while (FaxLogNext(_pappinfo, &tmpsf) == LOGERR_CONTINUEIT)
      do_custom_message(WM_FAXMSG, FAXLOGADD, (LPARAM)&tmpsf);
  }

  if (FaxLogInit(_pappinfo, SUBSCRIBE_LOG_COMPLETE) < MAXITERATORS) 
  {
    while (FaxLogNext(_pappinfo, &tmpsf) == LOGERR_CONTINUEIT)
      do_custom_message(WM_FAXMSG, FAXLOGADD, (LPARAM)&tmpsf);
  }

  if (FaxLogInit(_pappinfo, SUBSCRIBE_LOG_FAILED) < MAXITERATORS) 
  {
    while (FaxLogNext(_pappinfo, &tmpsf) == LOGERR_CONTINUEIT)
      do_custom_message(WM_FAXMSG, FAXLOGADD, (LPARAM)&tmpsf);
  }
  
  _port.reset();
  FaxEnumDevices(_pappinfo);
}

void TDDE_fax::update_log(WORD log)
{
  if (_log_mask)
  {
    const TString_array* arr = log2array(log);
    if (arr)
      _log_mask->update_log(log, *arr);
  }
}

void TDDE_fax::show_status(PSEND_FAX sf)
{                 
  if (_log_mask)
  {
    TString msg(80);
    switch(sf->fs)
    {       
      case FAXST_SEND_INIT     : msg = "Inizializzazione"; break;
      case FAXST_SEND_DIALING  : msg = "Composizione del numero "; 
                                 msg << sf->szDestFax; 
                                 break;       
      case FAXST_SEND_FCSI     : msg = "Connessione"; break;       
      case FAXST_SENDING       : msg = "Spedizione pagina "; 
                                 msg << sf->nTotCurPage << " di " << sf->nTotPages
                                     << ": " << sf->nPercent << '%';
                                 break;  
      case FAXST_PAGE_END      : msg = "Fine pagina "; msg << sf->nTotCurPage; break;
      case FAXST_COMPLETE      : msg = "Fine trasmissione"; break;     
      case FAXST_ABORT         : msg = "Operazione abortita"; break;
      case FAXST_PORTSHUT      : msg = "Chiusura COM"; msg << sf->nPort;
                                 if (sf->fe != FAXERR_OK)
                                   msg << ": " << error2string(sf->fe);
                                 break;   
      default                  : break;
    }
    if (msg.not_empty())
      _log_mask->set(F_STATUS, msg);
  }
}

void TDDE_fax::log_delete(WORD log, unsigned long id)
{   
  if (log == SUBSCRIBE_LOG_SENDING)
  {
    const bool ok = FaxCancel(_pappinfo, id);
    if (!ok)
      FaxLogDelete(_pappinfo, id, log);
  }
  else
    FaxLogDelete(_pappinfo, id, log);
}

void TDDE_fax::auto_configure()
{
  const int err = FaxAddDevice(_pappinfo, 0);
  if (err != 0)
  {                    
    const int seconds = 4;
    TProgind pi(seconds, "Ricerca dei Modem/Fax collegati...", FALSE, TRUE, 48);
    for (int i = 1; i <= seconds; i++)
    {                
      pi.setstatus(i);
      const clock_t end = clock() + CLOCKS_PER_SEC;
      while (clock() < end)
        do_events();
    }         
    
    _port.reset();                 // Azzera tutti i flag delle porte
    FaxEnumDevices(_pappinfo);     // Richiede la lista delle porte

    pi.set_text("Controllo dei Modem/Fax trovati ...");
    for (i = 1; i <= seconds; i++)
    {                
      pi.setstatus(i);
      const clock_t end = clock() + CLOCKS_PER_SEC;
      while (clock() < end)
        do_events();
    }         
    
    TString msg(24);
    msg << "Modem/Fax collegati: ";
    const long np = _port.ones();
    if (np == 0) 
      msg << "NESSUNO";
    else 
      msg << np;
    xvt_statbar_set(msg);
  }
  else
    error_box("Impossibile trovare il server Faxman");
}  

bool TDDE_fax::run_mask()
{   
  const bool ok = _log_mask == NULL;
  if (ok)
  {             
    TTemp_window tw(TASK_WIN);
    tw.maximize();
    _log_mask = new TLog_mask(this);
    
    update_log(SUBSCRIBE_LOG_PENDING );
    update_log(SUBSCRIBE_LOG_SENDING );
    update_log(SUBSCRIBE_LOG_FAILED  );
    update_log(SUBSCRIBE_LOG_COMPLETE);
    
    _log_mask->run();
    delete _log_mask;
    _log_mask = NULL;
  }
  return ok;
}

TString& TDDE_fax::ragsoc2name(TString& ragsoc) const
{
  TString first_name = ragsoc.left(30); first_name.trim(); 
  if (first_name.len() < 30)
  {
    TString last_name = ragsoc.mid(30); last_name.trim();
    ragsoc = first_name;
    ragsoc << ' ' << last_name;
  }  
  return ragsoc;
}

bool TDDE_fax::ricerca_clifo(char cf, const TString& codice, TFax_data& data) const
{
  TLocalisamfile f(LF_CLIFO);
  f.put(CLI_TIPOCF, cf);
  f.put(CLI_CODCF, codice);
  const int err = f.read();
  if (err == NOERR)
  {
    TToken_string s(80, ',');
    s = f.get("PFAX"); s.add(f.get("FAX"));
    data.set("DESTFAX", s);
    
    s = f.get("PTEL"); s.add(f.get("TEL"));
    data.set("DESTTEL", s);
    
    data.set("DESTNAME", "");
    
    s = f.get("RAGSOC");
    data.set("DESTFIRM", ragsoc2name(s));
              
    data.set("TIPO", cf == 'C' ? "Clienti" : "Fornitori");
    data.set(cf == 'C' ? "CLIENTI" : "FORNITORI", codice);
  }
  return err == NOERR;
}

bool TDDE_fax::ricerca_persone(char fg, const TString& codice, TFax_data& data) const
{
  TRelation r(LF_ANAG);
  
  if (fg == 'G')
    r.add(LF_ANAGGIU, "CODANAGR=CODANAGR");
  
  TRectype& c = r.curr();
  c.put(ANG_TIPOA, fg);
  c.put(ANG_CODANAGR, codice);
  
  const int err = r.read();
  if (err == NOERR)
  {
    TToken_string s(80, ',');
    s = c.get("PFAXRF"); s.add(c.get("FAXRF"));
    data.set("DESTFAX", s);
    
    s = c.get("PTELRF"); s.add(c.get("TELRF"));
    data.set("DESTTEL", s);
    
    if (fg == 'G')
    {
      data.set("DESTFIRM", c.get("RAGSOC"));
      data.set("DESTNAME", r.curr(LF_ANAGGIU).get("PERRIF"));
      
      data.set("TIPO", "Giuridiche");
      data.set("GIURIDICHE", codice);
    }
    else
    {                          
      data.set("DESTFIRM", "");
      s = c.get("RAGSOC");
      data.set("DESTNAME", ragsoc2name(s));
      
      data.set("TIPO", "Fisiche");
      data.set("FISICHE", codice);
    }  
  }
  return err == NOERR;
}

bool TDDE_fax::ricerca_ditte(const TString& codice, TFax_data& data) const
{
  TLocalisamfile f(LF_NDITTE);
  f.put(NDT_CODDITTA, codice);
  const int err = f.read();
  if (err == NOERR)
  {
    TToken_string s(128, ',');
    s.add(f.get(NDT_PFAX));
    s.add(f.get(NDT_FAX));
    data.set("DESTFAX", s);
    data.set("DESTNAME", f.get(NDT_PERRIF));
    data.set("DESTFIRM", f.get(NDT_RAGSOC));
    data.set("TIPO", "Ditte");
    data.set("DITTE", codice);
  }
  return err == NOERR;
}

bool TDDE_fax::set_destination(const TString& tipo, const TString& codice, TFax_data& data) const
{                                 
  const char* tipi[] = { "CLI", "DIT", "FIS", "FOR", "GIU", NULL };
  bool ok = TRUE;

  for (int i = 0; tipi[i]; i++)
  {
    if (tipo.compare(tipi[i], 3, TRUE) == 0)
      break;
  } 
  
  data = _queue.peek_last();  // Inizializza valori di default
  switch(i)
  {
  case 0 : ricerca_clifo('C', codice, data); break;
  case 1 : ricerca_ditte(codice, data); break;
  case 2 : ricerca_persone('F', codice, data); break;
  case 3 : ricerca_clifo('F', codice, data); break;
  case 4 : ricerca_persone('G', codice, data); break;
  default: ok = FALSE; break;
  }
  
  return ok;
} 

bool TDDE_fax::push_destination(const TString& tipo, const TString& codice)
{
  TFax_data* data = new TFax_data;
  const bool ok = set_destination(tipo, codice, *data);
  _queue.push(data);
  return ok;
}

bool TDDE_fax::can_close() const
{
  return _log_mask == NULL && _queue.empty() &&
         _pending.items() == 0 && _sending.items() == 0;
}

///////////////////////////////////////////////////////////
// Fax server application
///////////////////////////////////////////////////////////

class TFax_server : public TApplication
{                     
  TDDE_fax* _fax;

protected:
  virtual bool pre_create(); 
  virtual bool create(); 
  virtual bool destroy();
  virtual bool menu(MENU_TAG mt);
  virtual void on_firm_change();

public:
  TFax_server() : _fax(NULL) { } 
  virtual ~TFax_server() { } 
};

bool TFax_server::pre_create()
{ 
  CHECK(_fax == NULL, "Double fax creation");
  _fax = new TDDE_fax;
  return _fax->ok();
}

bool TFax_server::create()
{ 
  const bool ok = _fax->ok();
  if (ok) 
  {              
    bool show_status = FALSE;
    
    if (argc() >= 2 && stricmp(argv(1), "-S") == 0)
      show_status = TRUE;
    
    if (argc() > 2)
    {
      const TString tipo   = argv(1); 
      const TString codice = argv(2);
      _fax->push_destination(tipo, codice);
    }
    
    TMailbox mb;                  
    TToken_string topics = _fax->get_topics();
    for (TString topic = topics.get(0); topic.not_empty(); topic = topics.get())
    {
      TMessage* msg = mb.next_s(topic);
      while (msg)
      {
        const TString cmd(msg->body());
        if (_fax->do_initiate(0, topic))
          _fax->do_execute(0, cmd);
        msg = mb.next_s(topic);  
      }
    }  
    
    if (show_status)
    {
      dispatch_e_menu(MENU_ITEM(1));
    }  
    else
    {
      TTemp_window w(TASK_WIN);
      w.iconize();
    }
  }
  else 
    destroy();      // Non verrebbe mai chiamata
  return ok;  
}

bool TFax_server::destroy()
{   
  if (_fax) 
  {
    delete _fax;
    _fax = NULL;
  }  
  return TRUE;
}

void TFax_server::on_firm_change()
{          
  TApplication::on_firm_change();
  if (_fax)
    _fax->on_firm_change();
}

bool TFax_server::menu(MENU_TAG mt)
{   
  if (_fax)
  {    
    switch(mt)
    {
    case MENU_ITEM(1):
      _fax->run_mask(); 
      break;
    case MENU_ITEM(2):
      _fax->auto_configure();
      break;
    default:
      break;
    }  
  }  
  return TRUE;
}

int main(int argc, char** argv)
{         
  TApplication::check_parameters(argc, argv);

  char drive[_MAX_DRIVE];
  char path[_MAX_PATH];
  _splitpath(argv[0], drive, path, NULL, NULL);
  int err = _chdrive(toupper(*drive)-'A'+1);
  if (err == 0) 
  {
    const int last = strlen(path)-1;
    CHECKS(last >= 0, "Invalid directory ", path);
    if (path[last] == '\\' || path[last] == '/')
      path[last] = '\0';
    err = _chdir(path);
  }    
  
  if (err == 0)
  {
    TFax_server fs;
    fs.run(argc, argv, "Fax server");
  }
  else
    error_box("Impossibile entrare nella directory %s", (const char*)path);  
  return TRUE;
}
