#include <dongle.h>
#include <execp.h>
#include <msksheet.h>
#include <recarray.h>
#include <recset.h>
#include <relapp.h>
#include <sheet.h>
#include <urldefid.h>
#include <utility.h>

#include "../mg/anamag.h"

///////////////////////////////////////////////////////////
// TBrowse_button
///////////////////////////////////////////////////////////

TBrowse_button::TBrowse_button(TEdit_field* f) 
              : _fld(f)
{ }
  
TBrowse_button::~TBrowse_button()
{
}

// Certified 100%
TEditable_field& TBrowse_button::field(short id) const
{
  if (id > 0)
  {
    TMask_field& f = _fld->mask().field(id); 
    CHECKD(f.is_editable(), "Can't use in a browse the field ", id);
    return (TEditable_field&)f;
  }
  return *_fld;
}

///////////////////////////////////////////////////////////
// TList_sheet
///////////////////////////////////////////////////////////

// Certified 100%
TList_sheet::TList_sheet(TEdit_field* f, const char* caption, const char* head)
           : TBrowse_button(f), _row(-1), _caption(caption), _head(head)
{ }

// Certified 100%
TList_sheet::~TList_sheet()
{ }


// Certified 100%
void TList_sheet::parse_input(TScanner& scanner)
{
  _inp_id.add(scanner.pop());
}

// Certified 100%
void TList_sheet::parse_item(TScanner& scanner)
{
  _data.add(new TToken_string(scanner.string()));
}

// Certified 100%
void TList_sheet::parse_output(TScanner& scanner)
{
  _out_id.add(scanner.pop());
}

// il numero di riga selezionata
int TList_sheet::do_input()
{
  if (_inp_id.empty_items()) 
    return -2;          // List empty!

  _inp_id.restart();
  TToken_string rowsel(80);

  for (const char* fld = _inp_id.get(); fld; fld = _inp_id.get())
  {
    if (*fld == '"')
    {
      rowsel.add(fld+1);
      if (rowsel.not_empty()) rowsel.cut(rowsel.len()-1);
    }
    else
    {
      const short id = field().atodlg(fld);
      if (id > 0)
      { 
        const TMask_field& f = field(id);
        if (f.class_id() == CLASS_ZOOM_FIELD)
        {
          const TZoom_field& z = (TZoom_field&)f;
          rowsel.add(z.get_first_line());
        }
        else
           rowsel.add(f.get());
      }
      else rowsel.add("");
    }
  }

  TString fd, it;
  for (int i = 0 ; i < _data.items(); i++)
  {
    TToken_string& ts =_data.row(i);

    ts.restart();
    const char * item;
    for (item = rowsel.get(0); item ; item = rowsel.get())
    {
      it = item; it.trim();
      fd = ts.get(); fd.trim();
      if (fd != it) break;
    }
    if (!item) return i;
  }

  return -1;                 // Value not found!
}


// Certified 50%
void TList_sheet::do_output(CheckTime t)
{
  if (_row < 0 || t == FINAL_CHECK) 
    return;
  
  _out_id.restart();
  TToken_string& rowsel = _data.row(_row);
  rowsel.restart();
  for (const char* fld = _out_id.get(); fld; fld = _out_id.get())
  {
    const short id = field().atodlg(fld);
    TMask_field& f = field(id);
    const char* val = rowsel.get();
		if (t != STARTING_CHECK || f.field() == NULL)
		{           
			const bool hit = f.get() != val;
			f.set(val);
			if (hit && field().dlg() != id)
			{
				f.on_hit();
				if (t == RUNNING_CHECK)
					f.check();
			}
		}
  }
}


// Certified 100%
KEY TList_sheet::run()
{
  TArray_sheet sci(3, 3, -3, -3, _caption, _head);
  sci.rows_array() = _data;

  _row = do_input();
  sci.select(_row);
  const KEY k = sci.run();

  switch (k)
  {
  case K_ENTER:
    _row = (int)sci.selected();
    do_output();
    break;
  default:
    break;
  }

  return k;
}


// Certified 100%
bool TList_sheet::check(CheckTime t)
{                                       
  _row = do_input();
  bool passed = _row != -1;
  if (passed) 
    do_output(t);
  else
  {  
    switch(field().check_type())
    {
    case CHECK_SEARCH: passed = true; break;
    default: break;
    }
  }
  return passed;
}


///////////////////////////////////////////////////////////
// TBrowse
///////////////////////////////////////////////////////////

// Certified 100%
TBrowse::TBrowse(TEdit_field* f, TRelation* r, int key, const char* filter)
       : TBrowse_button(f),
         _relation(r), _cursor(new TCursor (r, "", key)),
         _filter(filter), _secondary(false), _alt_browse(NULL),
				 _custom_filter_handler(NULL)
{
	custom_cursor();
}


// Certified 100%
TBrowse::TBrowse(TEdit_field* f, TCursor* c)
       : TBrowse_button(f),
         _relation(NULL), _cursor(c), _secondary(false), _alt_browse(NULL),
				 _custom_filter_handler(NULL)
{
	custom_cursor();
}


// Certified 100%
TBrowse::~TBrowse()
{
  // Se e' stato usato il primo costruttore devo distruggere la relazione ed il cursore
  if (_relation)
  {
    delete _cursor;
    delete _relation;
  }
  if (_alt_browse)
    delete _alt_browse;
}

static bool descr_filter_handler(TMask_field& f, KEY k)
{
  if (k == K_SPACE)
  {
		TString expr;
    if (!f.get().empty())          // Filtro attivato!
		{
			const short id = f.dlg()-500;
			TString e = f.mask().get(id);  // Espressione regolare
			e.strip("\"'");                // Tolgo caratteri che potrebbero dare problemi  
			if (!e.blank())
				expr << "(DESCR+DESCRAGG)" << "?=\"" << e << '"';
			if (expr.empty())
				f.reset();
		}
    ((TBrowse_sheet&) f.mask()).add_custom_filter(expr);
  }
  return true;
}

void TBrowse::custom_cursor()
{
	TRelation * relation = _relation == NULL ? _cursor->relation() : _relation;
	int logicnum =  relation->lfile().num();

	switch(logicnum)
	{
		case LF_ANAMAG :
			if (_cursor->key() == 2 && ini_get_bool(CONFIG_DITTA, "Main", "CUSTOM_SEARCH_" TOSTRING(LF_ANAMAG), false, 2))
			{
				delete _cursor;
				_cursor = new TSorted_cursor(relation, ANAMAG_DESCR "|" ANAMAG_DESCRAGG "[1,50]", "", 2);
				set_custom_filter_handler(descr_filter_handler);
			}
			break;
		default:
			break;
	}
}

void TBrowse::custom_display()
{
	switch(_cursor->file().num())
	{
	case LF_ANAMAG:
		if (_cursor->key() == 2 && ini_get_bool(CONFIG_DITTA, "Main", "CUSTOM_SEARCH_" TOSTRING(LF_ANAMAG), false, 2))
		{
			TToken_string & it = (TToken_string &) items();
			if (it.find(ANAMAG_DESCRAGG) < 0)
			{
				const char * s = it.get(0);
				for (int i = 0; s && *s; s = it.get(++i))
					if (strcmp(s, ANAMAG_DESCR) == 0)
					{
						add_display_field("Descrizione aggiuntiva@50", ANAMAG_DESCRAGG, i + 1);
						break;
					}
			}
		}
		break;
	case LF_TABMOD:
    {
      _cursor->file().zero(); // Azzera il record corrente in modo da impostare "MOD"
      const TString& mod =_cursor->curr().get("MOD");
      const word cod = dongle().module_name2code(mod);
      field().set_module(cod);
    }
    break;
	default:
		break;
	}
}

// Certified 100%
void TBrowse::parse_display(TScanner& scanner)
{              
  const char* s = scanner.string();
  _head.add(dictionary_translate_header(s));
  s = scanner.line();
  _items.add(s);
}


void TBrowse::parse_input(TScanner& scanner)
{
  const char* s = scanner.pop();
  _inp_fn.add(s);

  s = scanner.pop();
  if (*s == '"')        // Constant string
  {
    scanner.push();
    TString& str = scanner.line();
    _inp_id.add(str);
  }
  else            // Field on the mask
  {                   
    CHECKS(_inp_id.get_pos(s) < 0, "Duplicate input field ", s);
    _inp_id.add(s);
    if (scanner.popkey() == "SE") 
      _inp_id << '@';       // Special FILTERing field
    else 
      scanner.push();
  }
}


void TBrowse::parse_output(TScanner& scanner)
{
  const char* s = scanner.pop();
#ifdef DBG      
  field().atodlg(s);
#endif  
  _out_id.add(s);
  s = scanner.pop();
  _out_fn.add(s);  
  _secondary = false;
}


bool TBrowse::parse_copy(const TString& what, const TBrowse& b)
{
  const bool all = what == "AL";
  if (all || what == "US")
  {
    set_insert(b.get_insert());
    _filter = b.get_filter();
    if (!field().has_warning() && b.field().has_warning()) 
      field().set_warning(b.field().get_warning());
    if (!all) return true;
  }
  if (all || what == "IN")
  {
    _inp_id = b._inp_id;
    _inp_fn = b._inp_fn;
    if (!all) return true;
  }
  if (all || what == "DI")
  {
    _head = b._head;
    _items = b._items;
    if (!all) return true;
  }
  if (all || what == "OU")
  {
    _out_id = b._out_id;
    _out_fn = b._out_fn;
    _secondary = b.field().has_check();
  }
  return true;
}


void TBrowse::parse_join(TScanner& scanner)
{
  TString80 j(scanner.pop());             // File or table

  CHECKS(_relation, "Can't join to NULL relation ", (const char*)j);

  int to;
  if (scanner.popkey() == "TO")         // TO keyword
  {
    const char* t = scanner.pop();
    to = name2log(t);
  }
  else
  {
    to = 0;                  // _relation->lfile()->num();
    scanner.push();
  }

  int key = 1;
  if (scanner.popkey() == "KE")
    key = scanner.integer();
  else scanner.push();

  int alias = 0;
  if (scanner.popkey() == "AL")
    alias = scanner.integer();
  else scanner.push();

  TToken_string exp(80);
  if (scanner.pop() == "INTO")
  {
    const char* r = scanner.pop();
    while (strchr(r, '=') != NULL)
    {
      exp.add(r);
      r = scanner.pop();
    }
  }
  scanner.push();

#ifdef DBG      
  if (exp.empty()) yesnofatal_box("JOIN senza espressioni INTO");
#endif  
               
  if (isdigit(j[0]))
    _relation->add(atoi(j), exp, key, to, alias);   // join file
  else
  {
#ifdef DBG      
    if (j.len() > 4)
      yesnofatal_box("'%s' non e' una tabella valida: %d", (const char*)j);
    else  
#endif          
      _relation->add(j, exp, key, to, alias);      // join table
  }
}


void TBrowse::parse_insert(TScanner& scanner)
{
  const TString& key = scanner.popkey();
  _insert.cut(0);
  if (key != "NO")
  {
    _insert << key[0] << scanner.line();
    _insert.trim();
  }
}

// Ritorna il numero di inputs senza contare quelli che funzionano solo da filtro
int TBrowse::input_fields()
{
  int inp = 0;
  for (const char* fld = _inp_id.get(0); fld; fld = _inp_id.get())
  {                                             
    if (*fld != '"' && strchr(fld, '@') == NULL)
    {
      TMask_field& f = field(field().atodlg(fld));
      if (f.active() && f.is_editable())
        inp++;
    }    
  }
  return inp;    
}

const char* TBrowse::get_input_fields() const
{
  return _inp_id;
}

const char* TBrowse::get_input_field_names() const
{
  return _inp_fn;
}

void TBrowse::add_input_field(const char * id, const char * name, const int pos, bool select)
{
	TString strid(id) ;

	if (select)
		strid << '@';
	if (pos < 0 || pos >= _items.items())
	{
		_inp_id.add(strid);
		_inp_fn.add(name);
	}
	else
	{
		_inp_id.insert_at(strid, pos);
		_inp_fn.insert_at(name, pos);
	}
}

void TBrowse::remove_display_field(const int pos)
{
	if (pos < 0)
	{
		_head.cut(0);
		_items.cut(0);
	}
	else
	{
		_head.destroy(pos);
		_items.destroy(pos);
	}
}
void TBrowse::copy_input(const TBrowse * b)
{
	if (b)
	{
		_inp_id = b->_inp_id;
		_inp_fn = b->_inp_fn;
	}
}

void TBrowse::copy_display(const TBrowse* b)
{
	if (b)
    b->get_display_fields(_head, _items);
}

void TBrowse::copy_output(const TBrowse * b)
{
	if (b)
	{
		_out_id = b->_out_id;
		_out_fn = b->_out_fn;
	}
}

void TBrowse::add_display_field(const char * hd, const char * name, const int pos)
{
	if (pos < 0 || pos >= _items.items())
	{
		_head.add(dictionary_translate_header(hd));
		_items.add(name);
	}
	else
	{
		_head.insert_at(dictionary_translate_header(hd), pos);
		_items.insert_at(name, pos);
	}
}

void TBrowse::remove_input_field(const int pos)
{
	if (pos < 0)
	{
		_inp_id.cut(0);
		_inp_fn.cut(0);
	}
	else
	{
		_inp_id.destroy(pos);
		_inp_fn.destroy(pos);
	}
}

void TBrowse::add_output_field(const char * id, const char * name, const int pos)
{
	if (pos < 0 || pos >= _items.items())
	{
		_out_id.add(id);
		_out_fn.add(name);
	}
	else
	{
		_out_id.insert_at(id, pos);
		_out_fn.insert_at(name, pos);
	}
}

void TBrowse::remove_output_field(const int pos)
{
	if (pos < 0)
	{
		_out_id.cut(0);
		_out_fn.cut(0);
	}
	else
	{
		_out_id.destroy(pos);
		_out_fn.destroy(pos);
	}
}


const char* TBrowse::get_output_fields() const
{
  return _out_id;
}

const char* TBrowse::get_output_field_names() const
{
  return _out_fn;
}

// @doc INTERNAL

// @mfunc Ritorna il numero di campi non vuoti e non filtrati
//
// @rdesc Numero di campi non vuoti e non filtrati
int TBrowse::do_input(
  bool filter) // @parm Indica se effettuare il filtro sulla selezione

  // @comm Questa funzione serve ai <c TCursor_sheet>
{
  int ne = 0;
  if (_inp_id.empty()) 
    return ne;

  _cursor->file(0).zero();  // was cur.zero() che non va bene per le tabelle di modulo
  TRectype& cur = _cursor->curr();
  TRectype filtrec(cur);

  _inp_id.restart();
  _inp_fn.restart();

  TString val;                  // Value to output
  bool tofilter = false;

  for (const char* fld = _inp_id.get(); fld; fld = _inp_id.get())
  {
    const TFieldref fldref(_inp_fn.get(), 0); // Output field

    if (*fld == '"')
    {
      val = (fld+1);
      if (val.not_empty()) val.rtrim(1);
      tofilter = filter;
    } 
    else
    {
      const TMask_field* campf = NULL;

      if (*fld == '-')
      {
        TSheet_field* sheet = field().mask().get_sheet();
        if (sheet != NULL)
        {
          const short id = atoi(fld+1);
          campf = &sheet->mask().field(id);
        }
      }
      else
      {
        const short id  = field().atodlg(fld);
        campf = &field(id);
      }

      if (campf != NULL)
      {
        const TMask_field& f = *campf;
        val = f.get();
            
        switch (f.class_id())
        {
        case CLASS_REAL_FIELD:
          // Cerco di allineare correttamente i campi interi salvati parzialmente su codtab
          if (fldref.to() > 1 && f.size() > 1 && val.full() && 
              ((TReal_field&)f).decimals() == 0 && fldref.name() == "CODTAB")
          {
            const int len = fldref.len(cur); 
            if (f.size() == len && val.len() < len)
              val.right_just(len);
          }
          break;
        case CLASS_DATE_FIELD:
          if (f.right_justified())
          {
            const TDate d(val);
            val = d.string(ANSI);
          }
          break;
        default:
          break;
        }
      
        const bool filter_flag = strchr(fld, '@') != NULL;
        tofilter = filter && filter_flag;
        if (f.is_edit() && val.not_empty() && !filter_flag) 
          ne++;          // Increment not empty fields count
      }
    }
    fldref.write(val, *_cursor->relation());
    if (tofilter)
    {
			const int len = fldref.len(cur);

      if (val.len() < len	&& cur.type(fldref.name()) == _alfafld)
        val.rpad(len, '~');
      fldref.write(val, filtrec);
    }
  }

  if (!filter) 
    return ne;

  TString work(_filter.size());
  bool filter_update = false;
  
  for (int i = 0; _filter[i]; i++)
  {
    if (_filter[i] == '"')
    { 
      do
      {
        work << _filter[i++];
      } while (_filter[i] && _filter[i] != '"');
      work << '"';
      if (!_filter[i]) break;
    }
    else
      if (_filter[i] == '#')
      {
				if (_filter[++i] == '-')
				{
					TString val;
	        TSheet_field* sheet = field().mask().get_sheet();

		      if (sheet != NULL)
			    {
			     const short id = atoi(&_filter[++i]);
			     
					 val = sheet->mask().field(id).get();
			   }
					work << '"' << val << '"';
			  }
				else
					work << '"' << field(atoi(&_filter[i])).get() << '"';
        while (isspace(_filter[i])) i++;
        while (isdigit(_filter[i])) i++;
        i--;
      }
      else
      {
        work << _filter[i];
        if (_filter[i] == '-' && _filter[i + 1] == '>')
          filter_update = true; 
      }
  }

	_cursor->relation()->mask2rel(field().mask());
  _cursor->setfilter(work, filter_update);
  _cursor->setregion(filtrec, filtrec);

  return ne;
}

static TBit_array s_checked;
static short s_checking = 0;

void TBrowse::do_output(CheckTime t)
{                   
  if (t == FINAL_CHECK) 
    return;

  const bool master = s_checking == 0;  
  if (master)  
  {
    s_checking = field().dlg();
    s_checked.reset();
    // Rendo intoccabili i campi del MIO output
    for (const char* fld = _out_id.get(0); fld && *fld; fld = _out_id.get())
    {
      const short id = field().atodlg(fld);
      s_checked.set(id);
    }
  }

  TString sum;
  TToken_string flds(24, '+');
  
  const TRelation& relation = *_cursor->relation();
  
  TBit_array spotted;
  
  _out_fn.restart();   
  const char* fld;
  for (fld = _out_id.get(0); fld && *fld; fld = _out_id.get())
  {
    const short id = field().atodlg(fld);
    TMask_field& f = field(id);

    flds = _out_fn.get();
    
    bool do_that = t != STARTING_CHECK || f.field() == NULL || (f.mask().mode() == MODE_INS && !f.in_key(0));
    if (do_that && main_app().class_id() == CLASS_RELATION_APPLICATION)  
    {
      // Considera a parte l'inizializzazione delle transazioni!
      // Non sovrascrivere con degli output campi che potrebbero essere riempiti dal .ini
      if (!f.empty() && f.field() != NULL)
      {
        const TMask& m = f.mask();
        if (!m.is_running() && m.get_sheet() == NULL) // Maschera principale chiusa
        {
          const TRelation_application& ra = (const TRelation_application&)main_app();
          if (ra.is_transaction())
            do_that = false;
        }
      }
    }
    if (do_that)
    {
      sum.cut(0);
      for(const char* fr = flds.get(0); fr; fr = flds.get())
      {  
        if (*fr == '"')
        {       
          sum << (fr+1);
          sum.rtrim(1);
        }
        else
        {
          const TFieldref fld(fr, 0);
          sum << fld.read(relation);
        }  
      }
                    
      bool hit = false;
      if (master)
      {     
        f.set(sum);
        hit = id != s_checking; // Il mio handler viene fatto nella on_key
      }
      else
      {     
        if (!s_checked[id])
        {
          f.set(sum);
          s_checked.set(id);
          hit = true;
        }  
      }
      spotted.set(id, hit);
    }
  }
  
  for (fld = _out_id.get(0); fld && *fld; fld = _out_id.get())
  {
    const short id = field().atodlg(fld);
    if (spotted[id])
    {
      TMask_field& f = field(id);
      f.check();
      f.on_hit();
    }
  }

  if (master)
    s_checking = 0;
}


void TBrowse::do_clear(CheckTime t)
{            
  const bool master = s_checking == 0;  
  if (master)  
  {
    s_checking = field().dlg();
    s_checked.reset();
    // Rendo intoccabili i campi del MIO input
    for (const char* fld = _inp_id.get(0); fld && *fld; fld = _inp_id.get())
    {         
      if (isdigit(*fld))
      {
        const short id = field().atodlg(fld);
        s_checked.set(id);
      }
    }
  }

  for (TString16 fld = _out_id.get(0); fld.not_empty(); fld = _out_id.get())
  {                      
    const short id = field().atodlg(fld);
    TMask_field& f = field(atoi(fld));
    if (f.field() == NULL && field().dlg() != id &&
        !s_checked[id] && _inp_id.get_pos(fld) < 0) 
    {
      f.reset();
      s_checked.set(id);
      f.on_hit();
      f.check(t);
    }
  }
  
  if (master)  
    s_checking = 0;  
}

bool TBrowse::do_link(bool insert)
{
  bool ok = false;
  TString app;
  if (_insert.starts_with("MTB", true))
    _cursor->file().get_relapp(app);
  else
    app = _insert.mid(1);
  if (app.find('#') >=  0)
  {
    const TString w(app);
    app = "";
    for (const char* f = w; *f; f++)
    {
      if (*f == '#') 
      {
        const int id = atoi(++f);
        app << field(id).get();
        while (isspace(*f)) ++f;
        while (isdigit(*f)) ++f; 
        if (*f)
          app << ' ' << *f;
        else 
          break;
      }            
      else
        app << *f;
    }
  } 
  
  TFilename msg; msg.temp("msg");
  app << " /i" << msg;

  if (insert)
  {
    TConfig ini(msg, "Transaction");
    ini.set("Action", TRANSACTION_RUN);
  }
  else
  {  
    TConfig ini(msg, "Transaction");
    ini.set("Action", TRANSACTION_LINK);

    TString8 paragraph; paragraph << _cursor->file().num();
    ini.set_paragraph(paragraph);

    // Uso sempre la chiave 1 per collegarmi agli altri programmi
    const TRelation& rel = *_cursor->relation();
    const RecDes& recd = rel.curr().rec_des(); // Descrizione del record della testata
    const KeyDes& kd = recd.Ky[0];             // Elenco dei campi della chiave 1
    TString inp_val;
    for (int i = 0; i < kd.NkFields; i++)
    {                        
      const int nf = kd.FieldSeq[i] % MaxFields;
      const RecFieldDes& rf = recd.Fd[nf];  
      const TFieldref fldref(rf.Name, 0);
      inp_val = fldref.read(rel);
      fldref.write(ini, paragraph, inp_val);
    }
  }

  TExternal_app a(app);
  a.run();
  field().mask().set_focus();

  if (msg.not_empty())
  {
    TConfig ini(msg, "Transaction");
    _rec = ini.get_long("Record");
    if (_rec > 0 || !insert)   // Modifica o cancellazione
      _cursor->update();       // Forza ricalcolo cursore
    if (_rec >= 0)
    {
      _cursor->file().readat(_rec);
      ok = _cursor->ok();
      if (ok) 
      {
        rec_cache(_cursor->file().num()).notify_change();  // Svuota eventule cache
        do_output();
      }    
    }
    ::remove(msg);
  }
  return ok;
}


TToken_string& TBrowse::create_siblings(TToken_string& siblings) const
{
  siblings = "";                      // Azzera la lista dei campi associati
  
  TBit_array key(4);                  // Elenco delle chiavi gia' utilizzate
  key.set(_cursor->key());

  TString fn;                         // Nome campo
  
  // Scorre la lista dei campi di output
  int n = 0;  
  TToken_string& outid = (TToken_string&)_out_id;
  for (const char* i = outid.get(0); i; i = outid.get(), n++)
  {
    const short id = field().atodlg(i);
    const TEditable_field& f = field(id);
    if (!f.active() || !f.is_edit())  // Scarta i campi non editabili
      continue;
    const TEdit_field& e = (const TEdit_field&)f;
    const TBrowse* b = e.browse();
    if (b == NULL) 
      continue;                       // Scarta i campi senza ricerca
    
    const TCursor* c = b->cursor();  
    
    // Considera ricerche sullo stesso file ma con chiave diversa
    if (c->file().num() == _cursor->file().num() && 
        (key[c->key()] == false || id == field().dlg()))      
    {
      _out_fn.get(n, fn);                     // Legge nome del campo su file          
      int pos = ((TToken_string&)_items).get_pos(fn);           // Determina header corrispondente
      if (pos < 0)                            // Se non lo trova identico ...
      {
        const int q = fn.find('[');
        if (q > 0)
        {
          fn.cut(q);
          pos = ((TToken_string&)_items).get_pos(fn);           // ... ritenta senza parentesi
        }
      }
      if (pos >= 0)
      {
        siblings.add(id);
        TString80 h; _head.get(pos, h); 
        siblings.add(h);
        const int et = siblings.find('@');
        if (et > 0) siblings.cut(et);
        key.set(c->key());                    // Marca la chiave come usata
      }  
    }
  }
  
  return siblings;
}


KEY TBrowse::run()
{    
  const TString& val = field().get();

  if (val.starts_with("*"))
  {
    TFuzzy_browse fb(&field(), cursor()->key());
    const KEY k = fb.run();
    if (k == K_ENTER)
    {
      do_input(true);
      _cursor->read(_isgteq);
      do_output();
    }
    return k;
  } else
  if (val.starts_with("%") && _alt_browse)
  {
    const KEY k = _alt_browse->run();
    if (k == K_ENTER)
    {
      do_input(true);
      _cursor->read(_isgteq);
      do_output();
    }
    return k;
  }

  begin_wait();
  
  do_input(true);
  _cursor->read(_isgteq);

  TString caption = _cursor->file().description();
  if (caption.blank()) 
    caption = TR("Selezione");
  
  KEY k = K_ESC;
  long selected = 0;

  TToken_string siblings, vals; 
  create_siblings(siblings);                            
  
  {
    byte buttons = 0;
    if (_insert.not_empty())
    {   
      // Mette il bottone di gestione, a meno che ...   
      if (_cursor->items() == 0) 
        buttons = 2; // Non mette il bottone collega se non ci sono elementi
      else 
        buttons = 3; 
    
      if (_insert[0] == 'M' || _insert[0] == 'R')
      {          
        const TString& maskname = field().mask().source_file();
        if (maskname.mid(2,2).compare("tb", 2, true) == 0 && field().in_key(0))
        {
          const char* tabname = _cursor->file().name();
          if (maskname.mid(4, 3).compare(tabname, 3, true) == 0) 
            buttons = 0;
        }
      }
    }  
  
    for (const char* i = _inp_id.get(0); i; i = _inp_id.get())
    {            
      if (*i != '\0' && *i != '"' && strchr(i, '@') == NULL)
      {
        const short id = field().atodlg(i);
        const TEditable_field& f = field(id);
        if (f.active() && f.is_editable()) 
        {           
          vals.add(i);
          vals.add(f.get());               
          vals.add((int)f.dirty());
        }
      }
    }  
  
    end_wait();

    TBrowse_sheet s(_cursor, _items, caption, _head, buttons, field(), siblings, _custom_filter_handler);

    k = s.run();
    selected = s.selected();
  }

  switch (k)
  {        
  case K_ESC:
  case K_QUIT:
    break;
  case K_CTRL+'G':    
    *_cursor = selected;
    k = do_link(false) ? K_ENTER : K_ESC;
    break;  
  case K_INS:
    k = do_link(true) ? K_ENTER : K_ESC;
    break;
  case K_ENTER:
    *_cursor = selected;
    do_output();
    break;
  default:
    {
      for (const char* i = vals.get(0); i && *i; i = vals.get())
      {
        const short id = field().atodlg(i);
        TEditable_field& f = field(id);
        f.set(vals.get());
        f.set_dirty(vals.get_int());
      }
    }
    if (k >= K_CTRL) // Scatta la ricerca su di una chiave alternativa
    {
      TMask& m = field().mask();                          
      const int tag = k - K_CTRL - K_F1;
      const short id = siblings.get_int(tag * 2);
      TEdit_field& ef = m.efield(id);
      ef.set_focus();
      k = K_F9;
      if (m.is_running())
        m.send_key(k, id, &ef); //m.send_key(k, id);
    }
    break;
  }
    
  return k;
}

void TBrowse::set_cursor(TCursor* c)
{
	if (_cursor != NULL)
		delete _cursor;
	_cursor = c ;
}

bool TBrowse::check(CheckTime t)
{
  bool passed = true;

  if (_secondary == true && t != RUNNING_CHECK)
    return true;

  CheckType chk = field().check_type();

  // Se ho la ricerca alternativa ed il campo comincia per % ...
  if (t == RUNNING_CHECK)
  {
    const TString& magic = field().get();
    if (magic[0] == '*' && cursor()->key() > 1)
    {
      TFuzzy_browse fb(&field(), cursor()->key());
      if (fb.check(t))
      {
        if (chk == CHECK_NONE) // Se trovo la chiave forzo gli output (RAGSOC in righe prima nota)
          chk = CHECK_NORMAL;
      }
      else
        return false;
    } else
    if (magic[0] =='%' && _alt_browse != NULL)
    {
      if (_alt_browse->check(t))
      {
        if (chk == CHECK_NONE) // Se trovo la chiave forzo gli output (RAGSOC in righe prima nota)
          chk = CHECK_NORMAL;
      }
      else
        return false;
    }
  }

  if (chk != CHECK_NONE)
  {
    const TMaskmode mode = (TMaskmode)field().mask().mode();
    if (chk == CHECK_REQUIRED && (t == STARTING_CHECK || mode == MODE_QUERY))
      chk = CHECK_NORMAL;
    
    const int ne = do_input(true);
    if (ne || chk == CHECK_REQUIRED)
    {               
      passed = _cursor->test() == NOERR;

      if (t != FINAL_CHECK)
      {
        if (passed)
        {           
          _cursor->repos();
          do_output(t);
					if (t == STARTING_CHECK && field().dirty() > 1)
						field().set_dirty(true);
        }
        else 
        {    
          if (chk == CHECK_SEARCH)
          {   
            passed = true;
          }
          else
          {
            do_clear(t);
            if (!field().mask().query_mode() && field().check_enabled())
              field().set_dirty(3);
          }    
        }  
      }
      else
      {
        if (chk == CHECK_SEARCH)
          passed = true;
      }
    }
    else
    {         
      if (chk == CHECK_SEARCH)
        passed = true;
      else
      {  
        if (t != FINAL_CHECK) 
          do_clear(t);
      }  
    }  
  }
  return passed;
}

bool TBrowse::empty_check()
{
  if (field().mask().query_mode() || field().check_type() != CHECK_REQUIRED)
    return true;
  else
    return do_input() > 0;
}

bool TBrowse::set_alt_browse(int altkey)
{
  if (_alt_browse)
    delete _alt_browse;
  if (altkey > 2)
    _alt_browse = new TAlternative_browse(&field(), altkey);
  return altkey > 2;
}

///////////////////////////////////////////////////////////
// TFile_select
///////////////////////////////////////////////////////////

TFile_select::TFile_select(TEdit_field* ef, const char* filter)
: TBrowse_button(ef), _filter(filter)
{ }

void TFile_select::parse_input(TScanner& scanner)
{
  scanner.pop();
}

void TFile_select::parse_output(TScanner& scanner)
{
  scanner.pop();
}
          
KEY TFile_select::run()
{
  TFilename path;
  path = field().get();
  if (path.full() && _filter.find('.') > 0 && !_filter.ends_with(".*"))
    path.ext(_filter.ext());

  FILE_SPEC fs; xvt_fsys_convert_str_to_fspec(path, &fs);
  
  bool good = xvt_dm_post_file_open(&fs, field().prompt()) == FL_OK;
  if (good)
  {
    xvt_fsys_convert_fspec_to_str(&fs, path.get_buffer(), path.size());
    good = _filter.blank() || xvt_str_match(path.name(), _filter, false);
    if (good)
      field().set(path);
    else
      field().error_box(FR("Il nome del file non corrisponde a %s"), _filter.get_buffer());
  }
  return good ? K_ENTER : K_ESC;
}

bool TFile_select::check(CheckTime ct)
{
  const TFilename name = field().get();
  if (ct != STARTING_CHECK && name.empty() && 
      field().check_type() == CHECK_REQUIRED)
    return false;
  bool ok = _filter.empty() || xvt_str_match(name, _filter, false);
  if (ok && field().roman())  // Must exist
    ok = name.exist();
  return ok;
}

///////////////////////////////////////////////////////////
// TDir_select
///////////////////////////////////////////////////////////

TDir_select::TDir_select(TEdit_field* ef) : TBrowse_button(ef)
{ }

void TDir_select::parse_input(TScanner& scanner)
{
  scanner.pop();
}

void TDir_select::parse_output(TScanner& scanner)
{
  scanner.pop();
}
          
KEY TDir_select::run()
{
  DIRECTORY savedir;
  xvt_fsys_get_dir(&savedir);

	DIRECTORY dir;
	xvt_fsys_convert_str_to_dir(field().get(), &dir);
  bool good = xvt_dm_post_dir_sel(&dir) == FL_OK;
  xvt_fsys_set_dir(&savedir);

  if (good)
  {
    TFilename path;
    xvt_fsys_convert_dir_to_str(&dir, path.get_buffer(), path.size());
    field().set(path);
  }
  return good ? K_ENTER : K_ESC;
}

bool TDir_select::check(CheckTime ct)
{
  const TFilename name = field().get();
  if (ct != STARTING_CHECK && name.empty() && 
      field().check_type() == CHECK_REQUIRED)
    return false;
  bool ok = true;
  if (field().roman())  // Must exist
    ok = name.exist();
  return ok;
}

///////////////////////////////////////////////////////////
// TProfile_select
///////////////////////////////////////////////////////////

TProfile_select::TProfile_select(TEdit_field* ef)
: TBrowse_button(ef)
{ }

void TProfile_select::parse_input(TScanner& scanner)
{
  scanner.pop();
}

void TProfile_select::parse_output(TScanner& scanner)
{
  scanner.pop();
}

HIDDEN int get_profile_desc(TConfig& cfg, void* jolly)
{
  const int num = atoi(cfg.get_paragraph());
  if (num > 0)
  {
    TString_array& p = *(TString_array*)jolly;
    TToken_string* str = new TToken_string;
    str->format("%4d", num);
    str->add(cfg.get("Description"));
    p.add(str);
  }
  return false;
}

int TProfile_select::get_descriptions(TString_array& a) const
{
  TFilename profname; 
  field().mask().make_profile_name(profname);
  TConfig prof(profname);
  a.destroy();
  prof.for_each_paragraph(get_profile_desc, &a);
  a.sort();
  return a.items();
}
          
KEY TProfile_select::run()
{                 
  TArray_sheet p(3, 3, -3, -3, TR("Profili"), HR("Codice@6R|Descrizione@60"), 0x6, 2);
  const short id = DLG_USER+1;
  TEdit_field& prompt = p.add_string(id, 0, PR("Salva con nome "), 1, 0, 60);
  p.add_button(DLG_SAVEREC, PR("~Registra"), K_CTRL+'r', TOOL_SAVEREC);
  prompt.set(field().get());
  
  TMask& m = field().mask();
  TFilename profname; m.make_profile_name(profname);
  
  bool running = true;
  KEY key;
  while (running)
  {
    p.destroy();  
    TString_array& a = p.rows_array(); 
    get_descriptions(a);
    p.field(DLG_SAVEREC).enable(a.items()>0);
    FOR_EACH_ARRAY_ROW_BACK(a, r, row)
      if (field().get() == row->get(1)) break;
    if (r)
      p.select(r);
  
    key = p.run();
    switch(key)
    {       
    case K_ENTER:      
      { 
        const int num = p.row().get_int(0);
        TString16 para; para << m.load_profile(num);
        prompt.set(p.row().get(1));
        running = false;
      }
      break;
    case K_CTRL+'r':             
      {    
        const TString& name = p.get(id);
        FOR_EACH_ARRAY_ROW_BACK(a, r, row)
          if (r != p.selected() && name == row->get(1)) break;
        if (r < 0)
        {
          const int num = p.row().get_int(0);
          m.save_profile(num, name);
          running = false;
        }
        else
          error_box("Esiste gia' un profilo di nome\n%s", 
                    (const char*)name);
      }
      break;
    case K_INS:
      {
        const TString& name = p.get(id);
        if (!name.blank())
        {
          FOR_EACH_ARRAY_ROW_BACK(a, r, row)
            if (name == row->get(1)) break;
          if (r < 0)  
          {
            TString16 para; para << m.save_profile(-1, name);
            field().set(name);
            running = false;
          }
          else
            error_box("Esiste gia' un profilo di nome\n%s", 
                      (const char*)name);
        }
        else
          error_box("E' necessario dare un nome al profilo");
      }
      break;
    case K_DEL:
      {                                 
        TString16 para; para << p.row().get_int(0);
        const TString desc = p.row().get(1);
        TConfig prof(profname, para);
        if (yesno_box("Confermare la cancellazione del profilo %s\n%s", 
                      (const char*)para, (const char*)desc))
          prof.remove_all();
      }
      break;
    default:
      running = false;
      break;
    }
  }
  field().set(prompt.get());
  return key;
}

bool TProfile_select::check(CheckTime ct)
{            
  switch (ct)
  {
  case STARTING_CHECK:
    {     
      TMask& m = field().mask();
      TFilename name; m.make_profile_name(name);
      TString16 para; para << m.load_profile();
      TConfig ini(name, para);
      field().set(ini.get("Description"));
    }
    break;
  case RUNNING_CHECK:
    if (!field().empty())
    { 
      const TString& name = field().get();
      TString_array a;
      get_descriptions(a);               
      FOR_EACH_ARRAY_ROW_BACK(a, r, row)      
      {
        if (name == row->get(1))
        {       
          field().mask().load_profile(row->get_int(0));
          break;
        }
      }
      if (r < 0)  
        return field().error_box("Profilo inesistente");
    }
    break;
  case FINAL_CHECK:
    if (!field().active())
    {
      TMask& m = field().mask();
      m.save_profile();
    }
    break;  
  default:
    break;
  }
  return true;
}

///////////////////////////////////////////////////////////
// TReport_select
///////////////////////////////////////////////////////////

TReport_select::TReport_select(TEdit_field* ef, const char* classe)
: TBrowse_button(ef), _classe(classe)
{ }

void TReport_select::parse_input(TScanner& scanner)
{
  scanner.pop();
}

void TReport_select::parse_output(TScanner& scanner)
{
  scanner.pop();
}
          
KEY TReport_select::run()
{
  TFilename path = field().get();
  if (select_custom_file(path, "rep", _classe))
  {
    path = path.name();
    path.ext("");
    field().set(path);
  }

  return path.not_empty() ? K_ENTER : K_ESC;
}

bool TReport_select::check(CheckTime ct)
{
  TFilename name = field().get();
  if (ct != STARTING_CHECK && name.empty() && 
      field().check_type() == CHECK_REQUIRED)
    return false;

  bool ok = true;
  if (field().roman())  // Must exist
    ok = name.custom_path();
  return ok;
}

///////////////////////////////////////////////////////////
//   TFuzzy_browse
///////////////////////////////////////////////////////////

static TFuzzy_browse* _curr_fbrowse = NULL;


TCursor& TFuzzy_browse::cursor()
{
  TBrowse& b = *field().browse();
  TCursor& c = *b.cursor();

  if (_altfld.empty())
  {
    const RecDes& rd = c.curr().rec_des();
    CHECKD(_altkey > 0 && _altkey <= rd.NKeys, "Invalid browse key ", _altkey);
    const KeyDes& kd = rd.Ky[_altkey-1];
    const int mf = kd.FieldSeq[kd.NkFields-1] % MaxFields;
    _altfld = rd.Fd[mf].Name;

    _outfld = "RAGSOC";
    TToken_string outfields = b.get_output_fields();
    const int outpos = outfields.get_pos(field().dlg());
    if (outpos >= 0)
    {
      outfields = b.get_output_field_names();
      outfields.get(outpos, _outfld);
    }
  }
  
  return c;
}

static void clean_string(TString& str)
{
  char* d = str.get_buffer();
  for (const char* s = d; *s; s++)
    if (*s < '\0' || isalnum(*s)) *d++ = *s;
  *d = '\0';
  str.upper();
}

TRecnotype TFuzzy_browse::find_magic(const TString& magic_val, double& best)
{
  const TBrowse& b = *field().browse();
  TCursor& c = cursor();

  c = 0L;
  TRecnotype recno = -1;
  TRectype& curr =  c.curr();

  curr.put(_altfld, magic_val);
  recno = c.read();
  if (recno >= 0 && curr.get(_altfld).starts_with(magic_val))
    return recno;

  recno = -1;
  best = 0.66;

  const int testlen = magic_val.len()+1;
  if (testlen > 3)
  {
    for (c = 0L; c.ok(); ++c)
    {
      TString80 val = curr.get(_altfld);
      clean_string(val);

      for (int i = val.find(magic_val[0], 0); i >= 0; i = val.find(magic_val[0], i+1))
      {
        double n = xvt_str_fuzzy_compare(val.mid(i, testlen), magic_val);
        n -= i*0.01;
        if (n > best)
        {
          best = n;
          recno = c.pos();
          if (n >= 1.0)
            break;
        }
      }
    }
  }

  if (recno >= 0)
    c = recno;
  else
    best = 0;
  return recno;
}

TRecnotype TFuzzy_browse::find(const TString& raw_val)
{
  TString80 magic_val = raw_val;
  clean_string(magic_val);

  double best = 0;
  TRecnotype recno = find_magic(magic_val, best);

  if (best < 0.8 && raw_val.find(' ') > 0)
  {
    magic_val = raw_val;
    magic_val.strip_double_spaces();
    const int spc = magic_val.find(' ');
    if (spc > 0)
    {
      const TString& left = magic_val.left(spc);
      const TString& right = magic_val.mid(spc+1);
      magic_val.cut(0) << right << ' ' << left; 
      clean_string(magic_val);
      double altbest = 0;
      const TRecnotype altrecno = find_magic(magic_val, altbest);
      if (altbest > best)
      {
        best = altbest;
        recno = altrecno;
      }
    }
  }

  return recno;
}

bool TFuzzy_browse::check(CheckTime /*t*/) 
{ 
  const TRecnotype recno = find(field().get());
  if (recno >= 0)
  {
    field().set(cursor().curr().get(_outfld));
    return true;
  }
  return run() == K_ENTER;
}

static bool fuzzy_code_handler(TMask_field& f, KEY k)
{
  if (k == K_F2)
  {
    f.reset();
    k = K_SPACE;
  }
  if (k == K_SPACE || k == K_TAB)
  {
    const TString& str = ((TEdit_field&)f).get_window_data();
    const TRecnotype pos = _curr_fbrowse->find(str);
    if (pos >= 0)
    {
      TSheet& s = (TSheet&)f.mask();
      if (k == K_TAB)
      {
        f.set(s.row(pos).get(0));
        s.post_select(pos);
      }
      else
        s.select(pos);
    }
  }

  return true;
}

KEY TFuzzy_browse::run()
{
  const TBrowse& b = *field().browse();
  TCursor& c = cursor();

  TString caption = c.file().description();
  if (caption.blank()) 
    caption = TR("Selezione");

  TToken_string fields, head;
  b.get_display_fields(head, fields);

  TCursor_sheet sheet(&c, fields, caption, head, 0, 2);
  TEdit_field& e = sheet.add_string(field().dlg(), 0, field().prompt(), 1, 1, field().size(), "U");
  e.set_handler(fuzzy_code_handler);

  _curr_fbrowse = this;

  TString80 val = field().get();
  val.strip("*");
  e.set(val);
  e.on_key(K_SPACE);
  sheet.first_focus(e.dlg());
  const KEY k = sheet.run();
  _curr_fbrowse = NULL;
  
  if (k == K_ENTER)
  {
    c = sheet.selected();
    const TFieldref fr(_outfld, 0);
    field().set(fr.read(*c.relation()));
  }
  return k;
}

TFuzzy_browse::TFuzzy_browse(TEdit_field* ef, int altkey) : TBrowse_button(ef), _altkey(altkey)
{
}


///////////////////////////////////////////////////////////
//   TAlternative_browse
///////////////////////////////////////////////////////////

static TBit_array _alternative_bits;

static bool alternative_filter(const TRelation* rel)
{
  const TRecnotype recno = rel->file().recno();
  return _alternative_bits[recno];
}

TCursor& TAlternative_browse::cursor()
{
  if (_cursor == NULL)
  {
    TBrowse& b = *field().browse();
    TCursor& c = *b.cursor();

    const RecDes& rd = c.curr().rec_des();
    const KeyDes& kd = rd.Ky[_altkey-1];
    const int mf = kd.FieldSeq[kd.NkFields-1] % MaxFields;
    _altfld = rd.Fd[mf].Name;

     _outfld = "RAGSOC";
    TToken_string outfields = b.get_output_fields();
    const int outpos = outfields.get_pos(field().dlg());
    if (outpos >= 0)
    {
      outfields = b.get_output_field_names();
      outfields.get(outpos, _outfld);
    }

    _cursor = new TCursor(c.relation(), "", _altkey);
    _alternative_bits.reset();
    const TRecnotype old = c.pos();
    for (c = 0L; c.ok(); ++c)
    {
      if (c.curr().get(_altfld).full())
        _alternative_bits.set(c.file().recno());
    }
    _cursor->set_filterfunction(alternative_filter);
    _cursor->items();
    _cursor->freeze();
    c = old;
  }
  return *_cursor;
}

static bool alternative_code_handler(TMask_field& f, KEY k)
{
  if (k == K_F2)
  {
    f.reset();
    k = K_SPACE;
  }
  if (k == K_SPACE || k == K_TAB)
  {
    const TString& str = ((TEdit_field&)f).get_window_data();
    const TRecnotype pos = _curr_fbrowse->find(str);
    if (pos >= 0)
    {
      TSheet& s = (TSheet&)f.mask();
      if (k == K_TAB)
      {
        f.set(s.row(pos).get(0));
        s.post_select(pos);
      }
      else
        s.select(pos);
    }
  }

  return true;
}

KEY TAlternative_browse::run()
{
  const TBrowse& b = *field().browse();
  TCursor& c = cursor();

  TString caption = c.file().description();
  if (caption.blank()) 
    caption = TR("Selezione");

  TToken_string fields, head;
  b.get_display_fields(head, fields);
  const int pos = fields.get_pos(_altfld);
  const int sz = c.curr().length(_altfld);

  if (pos > 0)
  {
    fields.destroy(pos); 
    fields.insert_at(_altfld, 0);
    TString80 h; head.get(pos, h);
    head.destroy(pos); 
    head.insert_at(h, 0); 
  }
  else
  {
    fields.insert_at(_altfld, 0);
    TString80 h; h << "Cod.Alt.@" << sz;
    head.insert_at(h, 0);
  }

  TCursor_sheet sheet(&c, fields, caption, head, 0, 2);
  TEdit_field& e = sheet.add_string(field().dlg(), 0, head.before('@'), 1, 1, sz, "U");
  e.set_handler(alternative_code_handler);

  _curr_fbrowse = this;

  TString80 val = field().get();
  val.strip("%");
  e.set(val);
  e.on_key(K_SPACE);
  sheet.first_focus(e.dlg());
  const KEY k = sheet.run();

  _curr_fbrowse = NULL;
  
  if (k == K_ENTER)
  {
    c = sheet.selected();
    field().set(c.curr().get(_outfld));
  }
  return k;
}

TAlternative_browse::TAlternative_browse(TEdit_field* ef, int altkey) : TFuzzy_browse(ef, altkey), _cursor(NULL)
{ }

TAlternative_browse::~TAlternative_browse()
{
  if (_cursor) 
    delete _cursor;
}