//      $Id: relapp.cpp,v 1.65 1995-08-29 09:17:39 gianluca Exp $        
#include <mailbox.h>
#include <sheet.h>
#include <urldefid.h>
#include <relapp.h>
#include <utility.h>
#include <xvtility.h>

#if XVT_OS == XVT_OS_WIN
#include <windows.h>
#endif

///////////////////////////////////////////////////////////
// Array delle chiavi della maschera di ricerca
///////////////////////////////////////////////////////////

class TChiave : public TObject
{
  enum { MAX = 16 };
  int _pos[MAX];
  byte _num;

public:
  void add(int p);
  int pos(byte n) const { return (n >= _num) ? -1 : _pos[n]; }
  byte items() const { return _num; }
  TChiave() : _num(0) {}
  virtual ~TChiave() {}
};

void TChiave::add(int p)
{
  CHECKD(_num < MAX, "Too many fields in key : field n.", p);
  _pos[_num++] = p;
}

class TKey_array : public TArray
{
  const TMask* _mask;

public:
  TKey_array(const TMask* m);
  virtual ~TKey_array() {}

  TChiave& key(byte k);
};

TChiave& TKey_array::key(byte k)
{
  k--;
  TChiave* chiave = (TChiave*)objptr(k);
  if (chiave == NULL)
  {
    chiave = new TChiave;
    add(chiave, k);
  }
  return *chiave;
}

TKey_array::TKey_array(const TMask* m) : _mask(m)
{
  const byte keys = m->num_keys();
  for (int f = 0; f < m->fields(); f++)
  {
    TMask_field& c = m->fld(f);
    if (c.in_key(0))
      for (byte k = 1; k <= keys; k++)
        if (c.in_key(k))
          key(k).add(f);
  }
}

///////////////////////////////////////////////////////////
// TRelation_application
///////////////////////////////////////////////////////////

TRelation_application::TRelation_application()
: _mask(NULL), _maskeys(NULL), _search_id(-1), _lnflag(FALSE)
{}

TRelation_application::~TRelation_application()
{
  if (_maskeys) delete _maskeys;
}

void TRelation_application::setkey()
{
  if (has_filtered_cursor())
  {
    TEdit_field* f = (TEdit_field*) get_search_field();
    TCursor* cur = f->browse()->cursor();
    cur->setkey();
    return;
  }
  file().setkey(1);
}


// what - meaning
//  0   - nop
//  1   - first
//  2   - last
//  3   - both
void TRelation_application::set_limits(byte what)
{
  if (has_filtered_cursor())
  {
    TEdit_field* f = (TEdit_field*)get_search_field();
    CHECK(f, "Manca il campo di ricerca");
    
    TBrowse* b = f->browse();
    TCursor* cur = b ? b->cursor() : NULL;
    if (cur)
    {
      cur->setkey();
      f->browse()->do_input(TRUE);
      if (cur->items() == 0) _first = _last = -1;
      else
      {
        if (what & 0x1)
        {
          *cur = 0;
          _first = cur->file().recno();
        }
        if (what & 0x2)
        {
          *cur = cur->items() - 1;
          _last = cur->file().recno();
        }
      }
      return;
    }
  }

  file().setkey(1);
  if (what & 0x1)
  {
    if (file().empty()) _first = 1;
    else
    {
      file().first();
      _first = file().recno();
    }
  }
  if (what & 0x2)
  {
    if (file().empty()) _last = 1;
    else
    {
      file().last();
      _last = file().recno();
    }
  }
}

bool TRelation_application::create()
{
  TApplication::create();
  const bool ok = user_create();
  if (ok)
  {
    write_enable();
    _mask = get_mask(MODE_QUERY);
    filter();
    _maskeys = new TKey_array(_mask);
    set_limits();
    dispatch_e_menu(BAR_ITEM(1));
  }
  return ok;
}


bool TRelation_application::menu(MENU_TAG m)
{
  if (m == BAR_ITEM(1))
    return main_loop();
  return TRUE;
}


bool TRelation_application::destroy()
{   
  if (_maskeys) delete _maskeys;
  user_destroy();
  return TApplication::destroy();
}


void TRelation_application::set_fixed()
{
  TString256 s;
  for (const char* f = _fixed.get(0); f && *f; f = _fixed.get())
  {
    s = f;
    const int u = s.find('=');
    const int id = atoi(s.left(u));
    const char* val = s.mid(u+1);
    if (*val)
      _mask->set(id, val);
    _mask->disable(id);
  }
}


void TRelation_application::enable_query()
{
  const bool query = _mask->query_mode(); 
  const bool keyon = query || get_relation()->status() == _isreinsert;

  for (byte k = 1; k <= _maskeys->items(); k++)
  {
    const TChiave& chiave = _maskeys->key(k);
    for (int i = 0; i < chiave.items(); i++)
    {
      const int num = chiave.pos(i);
      TMask_field& c = _mask->fld(num);
      if (c.enabled_default())
      {
        if (k == 1)
          c.enable(keyon);
        if (c.has_query())
          ((TEdit_field&)c).enable_check(query);
      }    
    }
  }

  set_fixed();
}


void TRelation_application::set_toolbar(bool all)
{
  const int mode = _mask->mode();
  int pos;

  if (all)
  {
    pos = _mask->id2pos(DLG_SAVEREC);
    if (pos >= 0) _mask->fld(pos).enable(mode != MODE_QUERY);
    pos = _mask->id2pos(DLG_DELREC);
    if (pos >= 0) 
    {               
      bool enabdel = mode == MODE_MOD;
      if (enabdel)
      {
        TRelation& r = *get_relation();
        const TRecnotype oldpos = r.lfile().recno();
        enabdel = !protected_record(r.curr());
        if (r.lfile().recno() != oldpos) 
          r.lfile().readat(oldpos);
      }  
      _mask->fld(pos).enable(enabdel);
    }
  }

  enable_query();
}


bool TRelation_application::save_and_new() const
{ return FALSE; }

int TRelation_application::set_mode(int mode)
{
  static int _mode = NO_MODE;
  if (mode < NO_MODE) mode = _mode;
  
  const int m = ((TMaskmode)mode == NO_MODE) ? (int) MODE_QUERY : mode;
  _mask->set_mode(m);

  if (mode == _mode)
  {
    set_toolbar(FALSE); // Fast buttons update
  }
  else
  {
    set_toolbar(TRUE);  // Full buttons update
    _mode = mode;
  }

  const char* t = "";
  switch(mode)
  {
  case MODE_QUERY: 
    t = "Ricerca"; break;
  case MODE_MOD: 
    t = "Modifica"; break;
  case NO_MODE: 
    t = "Ricerca/Inserimento"; break;
  case MODE_INS: 
    t = "Inserimento"; break;
  default: 
    break;
  }

  xvt_statbar_set(t, TRUE);

  return _mode;
}


bool TRelation_application::autonum(TMask* m, bool rec)
{
  TToken_string k(get_next_key());
  
  if (!rec && !m->query_mode()) 
    m->reset();
  _renum_message = "";
  
  for (const char* n = k.get(0); n && *n; n = k.get())
  {
    const short id = atoi(n);                                                       
    CHECKD (id > 0, "Identificatore di autonumerazione errato: ", id);
    const char* val = k.get();
    TMask_field& f = m->field(id);
    if (rec || f.get().empty()) f.set(val);
    if (rec) f.autosave(get_relation());
    if (_renum_message.empty() || f.in_key(1))
      _renum_message.format("Il documento e' stato registrato con :\n  %s = %s", (const char *) f.prompt(), (const char *) f.get());
  }
  return k.not_empty();
}


void TRelation_application::query_mode(bool pre_ins)
{
  TMask* old = _mask;            
  const bool was_open = old != NULL && old->is_open();
  const bool changing = changing_mask(MODE_QUERY);

  if (changing && was_open)
    old->close_modal();

  _mask = get_mask(MODE_QUERY);
  if (changing)
  {
    if (was_open) 
      _mask->open_modal();
    if (_maskeys) delete _maskeys;
    _maskeys = new TKey_array(_mask);
    set_limits();
  }

  _mask->reset();

  if (pre_ins)
  {
    set_mode(NO_MODE);
    init_query_insert_mode(*_mask);
  }
  else
  {
    set_mode(MODE_QUERY);
    init_query_mode(*_mask);
  }
}


void TRelation_application::insert_mode()
{               
  if (_mask->query_mode())
  {
    if (test_key(1, FALSE) == FALSE)
    {
      if (!autonum(_mask, FALSE))
      {
        query_insert_mode();
        return;
      }
    }
    
    const bool changing = changing_mask(MODE_INS);
    TFilename workname; workname.temp("msk");
    if (changing)
    {
      _mask->set_workfile(workname);
      _mask->save();
      _mask->close_modal();
    }
    _mask = get_mask(MODE_INS);
    if (changing)
    {
      _mask->reset();
      _mask->set_workfile(workname);
      _mask->load();
      ::remove(workname);
      _mask->open_modal();
      delete _maskeys;
      _maskeys = new TKey_array(_mask);
    }
  }
  else
  {
    if (!autonum(_mask, FALSE))
    {
      query_insert_mode(); 
      return;
    }  
  }
  
  set_mode(MODE_INS);

  get_relation()->zero();          // Azzera tutta la relazione!
  init_insert_mode(*_mask);
}

bool TRelation_application::modify_mode()
{
  int err = get_relation()->read(_isequal, _testandlock);
  if (err != NOERR)
  {
    if (err == _islocked)
      message_box("I dati sono gia' in uso ad un altro programma");
    else
      error_box("Impossibile leggere i dati: errore %d", err);
    query_mode();
    return FALSE;
  }

  const bool changing = changing_mask(MODE_MOD);
  if (changing)
    _mask->close_modal();

  _mask = get_mask(MODE_MOD);
  
  if (changing)
  {
    _mask->open_modal();                
    delete _maskeys;                
    _maskeys = new TKey_array(_mask);
  }
  
  set_mode(MODE_MOD);

  err = read(*_mask);
  if (err != NOERR)
  {
    query_mode();
    return FALSE;
  }

  get_relation()->save_status();
  init_modify_mode(*_mask);
  return TRUE;
}


TMask_field* TRelation_application::get_search_field() const
{
  if (_search_id > 0) 
    return &_mask->field(_search_id);
  
  const TChiave& k = _maskeys->key(1);
  for (int i = 0; i < k.items(); i++)
  {
    TMask_field* f = &_mask->fld(k.pos(i));
    if (f->required()) return f;
  }
  return NULL;
}

bool TRelation_application::search_mode()
{
  if (_mask->mode() != MODE_QUERY)
    query_mode();

  TMask_field* prima = get_search_field();
  while (prima)
  {
    if (prima->on_key(K_F9))
    {
      if (find(1))
        return modify_mode();
    }        
    
    TMask_field* dopo = &_mask->fld(_mask->focus_field());
    prima = (dopo == prima) ? NULL : dopo;
  }
  return FALSE;
}


bool TRelation_application::test_key(byte k, bool err)
{
  const TChiave& chiave = _maskeys->key(k);
  bool onereq = FALSE, onefill = FALSE;

  for (int i = 0; i < chiave.items(); i++)
  {
    const int num = chiave.pos(i);
    TMask_field& c = _mask->fld(num);
    
    if (c.required())
    {
      onereq = TRUE;
      if (c.get().empty())
      {
        if (err)
        {
          _mask->first_focus(-c.dlg());
          error_box("Manca un valore indispensabile per la ricerca");
        }
        return FALSE;
      }
    }
    else
      /*      if (k == 1 && !onereq && !onefill && c.get().not_empty()) */
      if (!onereq && !onefill && c.is_edit() && c.get().not_empty()) 
        onefill = TRUE;
  }
  if (k == 1 && !onereq && !onefill)
  {
    if (err)
      error_box("Manca un valore indispensabile per la ricerca");
    return FALSE;
  }
  return onefill || onereq;
}

// Guy: doesn't change fields
bool TRelation_application::find(byte k)
{
  const byte numkeys = _maskeys->items();

  if (k == 0)
  {
    for (k = 1; k <= numkeys && !test_key(k, FALSE); k++);
    if (k > numkeys)
      return test_key(1, TRUE);
  }

  file().setkey(k);
  file().zero();
  const TChiave& chiave = _maskeys->key(k);
  for (int i = 0; i < chiave.items(); i++)
  {
    const TMask_field& c = _mask->fld(chiave.pos(i));
    if (c.shown())                        // Ignora campi invisibili
      c.autosave(get_relation());
  }

  const int err = file().read(_isequal);
  return err == NOERR;
}


bool TRelation_application::save(bool check_dirty)
{
  static bool was_dirty = FALSE;
  
  int err = NOERR;
  const int mode = _mask->mode();

  if (check_dirty)
  {
    const int dirty = _mask->dirty();

    const char* ms = (mode == MODE_MOD) ? "le modifiche" : "i dati inseriti";
    
    if (mode == MODE_QUERY)
    {
      const bool cont = !dirty || yesno_box("Annullare %s?", ms);
      return cont;
    }

    if (!dirty && !was_dirty) 
    {  
      if (mode == MODE_MOD)
      {
        get_relation()->restore_status();
        get_relation()->lfile().reread(_unlock);           // Unlock main file
      }
      return TRUE;
    }
    
    const KEY last = _mask->last_key();
    const bool annulla = last == K_ESC || last == K_QUIT || last == K_F9;
    const bool errore = dirty && _mask->field(dirty).dirty() > TRUE;
    
    KEY k; 
    if (errore)
    {
      if (annulla) 
      { 
        TString w(_mask->field(dirty).warning());
        
        if (w.empty()) 
          w = "Campo inconsistente";
        k = yesno_box("%s: annullare?", (const char *) w) ? K_ESC : K_NO;
        if (k == K_NO) _mask->first_focus(-_mask->field(dirty).dlg());
      }  
      else k = K_ESC;
    }
    else
      k = yesnocancel_box("Registrare %s?", ms); 
    
    if (k == K_ESC || k == K_NO)
    {                   
      if (mode == MODE_MOD)
      {
        get_relation()->restore_status();
        get_relation()->lfile().reread(_unlock);     // Unlock main file
      }  
      was_dirty = FALSE;
      return k == K_NO;
    }        

    if (annulla)
    {
      if (!_mask->check_fields())                   // Exit with ESC didn't check values
      {                       
        _mask->first_focus(-_mask->fld(_mask->focus_field()).dlg());
        was_dirty = TRUE; 
        return FALSE;
      } 
    }   
  }
  was_dirty = FALSE;
  
  begin_wait();
  if (mode == MODE_INS)
  {
    bool changed = TRUE;        
    bool changed_key = FALSE;

    while (changed)
    {
      err = write(*_mask);
      if (err == _isreinsert)
      {
        changed = autonum(_mask, TRUE);
        if (!changed) 
        {
          _mask->disable_starting_check();
          enable_query();             // Abilita chiave 1 per rinumerazione manuale
        }
        else
          changed_key = TRUE;
      }  
      else
        changed = FALSE;
    }
    if (err == NOERR)
    {
      if (changed_key)
        message_box(_renum_message);
      get_relation()->save_status();
      set_limits();
      get_relation()->restore_status();
    }
  }
  else 
  {  
    get_relation()->restore_status();
    err = rewrite(*_mask);
  }
  end_wait();
  
  switch(err)
  {
  case NOERR:
    _recins = get_relation()->lfile().recno();
    break;
  case _isreinsert:
    warning_box("Esiste gia' un documento con la stessa chiave");
    break;
  default:
    error_box("Impossibile registrare i dati: errore %d", err);
    break;
  }
  return err == NOERR;
}


int TRelation_application::read(TMask& m)
{
  const TRelation *r = get_relation();
  m.autoload(r);
  return NOERR;
}


int TRelation_application::write(const TMask& m)
{
  TRelation *r = get_relation();
  m.autosave(r);
  r->write();    
  return r->status();
}


int TRelation_application::rewrite(const TMask& m)
{
  TRelation *r = get_relation();
  m.autosave(r);
  r->rewrite();
  return r->status();
}


bool TRelation_application::relation_remove()
{
  CHECK(_mask->mode() == MODE_MOD, "You can call remove in MODE_MOD only");
  TRelation *r = get_relation();

  r->restore_status();

  if (protected_record(r->curr()))
    return message_box("Registrazione non eliminabile");

  if (yesno_box("Confermare l'eliminazione"))
  {
    r->restore_status();
    const bool ok = remove();
    if (ok)
      set_limits();
    else
      return error_box("Errore di cancellazione %d", r->status());
  }
  return TRUE;
}


bool TRelation_application::remove()
{
  const int err = get_relation()->remove();
  return err == NOERR;
}

bool TRelation_application::firm_change_enabled() const
{
  bool ok = TApplication::firm_change_enabled();
  if (ok) ok = curr_mask().query_mode();
  return ok;
}

bool TRelation_application::main_loop()
{ 
  _recins = -1;
  
  query_mode();
  _mask->open_modal();

  KEY k;
  
  // Provoca l'autopremimento per il messaggio di LINK
  if (_lnflag) _mask->send_key(K_AUTO_ENTER, 0);
  
  do
  {
    const bool change = firm_change_enabled();
    // Dis/abilita cambio ditta
    enable_menu_item(M_FILE_NEW, change);
    // Dis/abilita cambio parametri
    enable_menu_item(M_FILE_REVERT, change);

    k = _mask->run();

    switch (k)
    {
    case K_ESC:
      if (save(TRUE))
        query_mode();
      if (_lnflag) 
        k = K_QUIT;
      break;                             
    case K_QUIT:      
      if (!save(TRUE))
        k = K_ENTER;
      break;
    case K_ENTER:
      if (find(0)) modify_mode();
      else insert_mode();
      break;
    case K_SAVE:
      if (save(FALSE))
      {
        if (_autoins_caller.not_empty())
        {
          k = K_QUIT;
        }
        else
        {
          if (save_and_new())
          {
            if (_mask->insert_mode())
              insert_mode();
            else
              query_mode();
          }  
          else  
            modify_mode();
        }  
      }
      break;
    case K_INS:
      if (_mask->query_mode() || save(TRUE))
        insert_mode();
      break;
    case K_DEL:
      if (relation_remove())
        query_mode();
      if (_autoins_caller.not_empty())
      {    
        if (_lnflag) _recins = 0;
        k = K_QUIT;
      }  
      break;
    case K_F9:
      if (save(TRUE))
        search_mode();
      break;
    default:
      if (save(TRUE))
      {
        setkey();
        int err = ~NOERR;
        switch (k)
        {
        case K_HOME:
          err = file().readat(_first, _testandlock);
          break;
        case K_NEXT:
          err = file().reread();
          err = file().next(_testandlock);
          break;
        case K_PREV:
          err = file().reread();
          err = file().prev(_testandlock);
          break;
        case K_END:
          err = file().readat(_last, _testandlock);
          break;
        default:
          break;
        }
        if (err == NOERR || err == _islocked) modify_mode();
        else query_mode();
      }
      break;
    }
  } while (k != K_QUIT);

  if (_mask->is_open())
    _mask->close_modal();

  _mask->set_mode(NO_MODE);

  if (autoins_caller().not_empty() && _recins >= 0)
  {
    TMessage msg(autoins_caller(), _lnflag ? MSG_LN : MSG_AI, format("%ld", _recins));
    msg.send();
  }

  return k != K_QUIT;
}

bool TRelation_application::filter()
{
  TMailbox mail;
  TMessage* msg = mail.next_s(MSG_FS);

  if (msg)
  {
    _mask = get_mask(MODE_MOD);
    TToken_string body = msg->body();

    short id = body.get_int();
    while (id > 0)
    {
      _search_id = id;
      TEdit_field& f = (TEdit_field&)_mask->field(id);
      TCursor* cur = f.browse()->cursor();
      TRectype& rec = cur->curr();
      rec.zero();
      
      TString80 t;
      const char* s;
      while((s = body.get()) != NULL)
      {
        t = s;
        const int u = t.find('=');
        if (u < 0)
        {
          id = atoi(t);
          break;
        }
        _fixed.add(t);
        const short fid = atoi(t.left(u));
        const TFieldref* campo = _mask->field(fid).field();
        if (campo != NULL)
          campo->write(t.mid(u+1), rec);
      }
      cur->setfilter("");
      cur->setregion(rec, rec);
      if (s == NULL) id = 0;
    }
  }

  mail.restart();
  msg = mail.next_s(MSG_AI);
  if (msg) _autoins_caller = msg->from();

  mail.restart();
  msg = mail.next_s(MSG_LN);
  if (msg) 
  {
    TToken_string body(msg->body());
    const int key = body.get_int();
    
    _autoins_caller = msg->from();
    _lnflag = TRUE;
    
    const char* v = body.get();
    TString80 s;
    for (int i = 0; v != NULL && i < _mask->fields(); i++)
    {
      TMask_field& f = _mask->fld(i);
      
      if (f.active() && f.dlg() > 0 && f.in_key(key))
      {        
        s = v;                
        _fixed.add(format("%d=%s", f.dlg(), (const char*)s));
        v = body.get();
      }
    }
  }

  return TRUE;
}

void TRelation_application::set_link(TMask & m, const char * keyexpr)

{
  CHECK(keyexpr != NULL, "Invalid expression");
  TToken_string body(keyexpr);
  const int key = body.get_int();
  
  _lnflag = TRUE;
  
  const char* v = body.get();
  
  const int max = m.fields();
  for (int i = 0; i < max && v != NULL; i++)
  {
    TMask_field& f = m.fld(i);
    
    if (f.active() && f.dlg() > 0 && f.in_key(key))
    {                          
      const TString s(v);                        
      _fixed.add(format("%d=%s", f.dlg(), (const char*) s));
      v = body.get();
    }
  }
}