#include <ctype.h>
#include <stdlib.h>

#include <applicat.h>
#include <form.h>
#include <msksheet.h>
#include <relation.h>
#include <sheet.h>
#include <utility.h>

#include "../ba/ba2100.h"

static TForm* _form = NULL;

static TForm& form()
{
  CHECK(_form, "Can't print NULL form");
  return *_form;
}

///////////////////////////////////////////////////////////
// TForm_flags
///////////////////////////////////////////////////////////

struct TForm_flags : public TObject
{
  bool automagic : 1;
  bool enabled   : 1;
  bool shown     : 1;

protected:
  void print_on(ostream& out) const;

public:
  TForm_flags();

  void print_on(TMask& m);
  void read_from(const TMask& m);

  bool update(const char* s);
};

TForm_flags::TForm_flags()
{
  automagic = FALSE;
  shown = enabled = TRUE;
}

// Read from string
// Certified 100%
bool TForm_flags::update(const char* s)
{
  CHECK(s, "NULL flags string");
  for (; *s; s++) switch(toupper(*s))
  {
  case 'A': 
    automagic = TRUE; break;
  case 'D': 
    enabled = FALSE; break;
  case 'H': 
    shown = FALSE; break;
    default : 
    error_box("Unknown form item flag '%c'", *s); break;
  }
  return TRUE;
}


// Print on stream
// Certified 100%
void TForm_flags::print_on(ostream& out) const
{
  TString16 s;

  if (automagic) s << "A";
  if (!enabled) s << "D";
  if (!shown) s << "H";

  if (s.not_empty()) 
    out << " FLAGS \"" << s << '"' << endl;
}

// Set mask fields
// Certified 100%
void TForm_flags::print_on(TMask& m)
{
  m.set(F_DISABLED,  enabled   ? " " : "X");
  m.set(F_HIDDEN,    shown     ? " " : "X");
  m.set(F_AUTOMAGIC, automagic ? "X" : " ");
}


// Get mask fields
// Certified 100%
void TForm_flags::read_from(const TMask& m)
{
  shown     = !m.get_bool(F_HIDDEN);
  enabled   = !m.get_bool(F_DISABLED);
  automagic = m.get_bool(F_AUTOMAGIC);
}


///////////////////////////////////////////////////////////
// TForm_item
///////////////////////////////////////////////////////////

class TForm_item : public TObject
{
  TPrint_section* _section;
  TString _desc;
  TForm_flags _flag;
  TBit_array _group;
  TString_array _message;

protected:
  int _x, _y, _width, _height;
  TString _prompt;

  virtual void print_on(ostream& out) const;
  virtual void print_body(ostream& out) const;

  int width() const { return _width; }
  int height() const { return _height; }

  bool shown() const { return _flag.shown; }
  bool hidden() const { return !_flag.shown; }
  bool enabled() const { return _flag.enabled; }
  bool disabled() const { return !_flag.enabled; }
  bool automagic() const { return _flag.automagic; }

  virtual bool parse_head(TScanner&);
  virtual bool parse_item(TScanner&);

  virtual const char* get() const { return _prompt; }
  virtual bool set(const char* s) { _prompt = s; return TRUE; }

  TToken_string& message(int m = 0);
  void send_message(const TString& cmd, TForm_item& dest) const;
  bool do_message(int m = 0);

  void string_at(int x, int y, const char* s);

public:
  virtual bool parse(TScanner&);
  virtual bool update();

  virtual void print_on(TMask& m);
  virtual void read_from(const TMask& m);
  bool edit(TMask& m);

  TPrint_section& section() const { return *_section; }

  bool in_group(byte g) const { return g == 0 || _group[g]; }
  const TString& key() const { return _desc; }
  void print_on(TToken_string& row) const;

  void show(bool on = TRUE) { _flag.shown = on; }
  void hide() { show(FALSE); }
  void enable(bool on = TRUE);
  void disable() { enable(FALSE); }

  TForm_item(TPrint_section* section);
  virtual ~TForm_item() {}
};


TForm_item::TForm_item(TPrint_section* section)
: _section(section), _x(-1), _y(-1), _width(0), _height(0)
{}


bool TForm_item::parse_head(TScanner& scanner)
{
  _width  = scanner.integer();
  if (_width) _height = scanner.integer();
  return TRUE;
}


void TForm_item::print_on(ostream& out) const
{
  out << class_name();
  if (_width > 0)
  {
    out << ' ' << _width;
    if (_height > 0)
      out << ' ' << _height;
  }
  out << "\nBEGIN\n";

  print_body(out);

  out << "END\n" << endl;
}


void TForm_item::print_body(ostream& out) const
{
  out << " KEY \"" << _desc << "\"\n";

  if (_y >= 0)
    out << " PROMPT " << _x << ' ' << _y << " \"" << _prompt << "\"\n";

  if (_group.ones())
    out << " GROUP " << _group << "\n";

  out << _flag;
  
  if (_message.items() == 1)
    out << " MESSAGE " << (TToken_string&)_message[0] << endl;
}


bool TForm_item::parse_item(TScanner& scanner)
{
  if (scanner.key() == "PR")
  {
    _x = scanner.integer();
    _y = scanner.integer();
    _prompt = scanner.string();
    return TRUE;
  }

  if (scanner.key() == "FL")
    return _flag.update(scanner.string());

  if (scanner.key() == "ME")
  {
    TFixed_string m(scanner.line());
    m.strip_spaces();
    message(0).add(m);
    return TRUE;
  }
  
  if (scanner.key() == "KE")
  {
    _desc = scanner.string();
    return TRUE;
  }

  if (scanner.key() == "GR")
  {
    _group.set(scanner.line());
    return TRUE;
  }

  yesnofatal_box("Unknown symbol in item '%s': '%s'",
                 (const char*)key(), (const char*)scanner.token());

  return FALSE;
}

bool TForm_item::parse(TScanner& scanner)
{
  bool ok = parse_head(scanner);

  if (ok && scanner.popkey() != "BE")
    ok = yesnofatal_box("Missing BEGIN in form item %s", (const char*)key());

  while (ok && scanner.popkey() != "EN")
    ok = parse_item(scanner);

  return ok;
}

void TForm_item::enable(bool on)
{
  _flag.enabled = on;
  show(on);
}


void TForm_item::string_at(int x, int y, const char* s)
{
  if (hidden()) return;
  
  section().offset(x, y);
  TPrintrow& row = section().row(y-1);         // Seleziona riga di stampa

  if (_width > 0 && strlen(s) > (word)_width)  // Tronca testo se necessario
  {
    strncpy(__tmp_string, s, width());
    __tmp_string[width()] = '\0';
    s = __tmp_string;
  }
  row.put(s, x-1);                             // Stampa testo
}


TToken_string& TForm_item::message(int m)
{
  TToken_string* t = (TToken_string*)_message.objptr(m);
  if (t == NULL)
  {
    t = new TToken_string(16);
    _message.add(t, m);
  }
  return *t;
}


void TForm_item::send_message(const TString& cmd, TForm_item& des) const
{
  if (cmd == "ADD" || cmd == "SUM" || cmd == "INC")
  {
    const real n((cmd[0] == 'I') ? "1.0" : get());
    real r(des.get());
    r += n;
    des.set(r.string());
  } else
    if (cmd == "COPY")
    {
      des.set(get());
    } else
      if (cmd == "APPEND")
      {
        TString256 val = des.get();
        if (val.not_empty()) val << ' ';
        val << get();
        des.set(val);
      } else
        if (cmd == "DISABLE")
        {
          des.disable();
        } else
          if (cmd == "ENABLE")
          {
            des.enable();
          } else
            if (cmd == "HIDE")
            {
              des.hide();
            } else
              if (cmd == "RESET")
              {
                des.set("");
              } else        
                if (cmd == "SHOW")
                {
                  des.show();
                } else
                  if (cmd[0] == '"')
                  {
                    TString256 val(cmd);
                    val.strip("\"");
                    des.set(val);
                  } else
                    error_box("Unknown message in item '%s': '%s'",
                              (const char*)key(), (const char*)cmd);
}


bool TForm_item::do_message(int num)
{
  TToken_string& messaggio = message(num);
  if (messaggio.empty()) return FALSE;

  TToken_string msg(16, ',');
  for (const char* m = messaggio.get(0); m; m = messaggio.get())
  {
    msg = m;
    if (*m == '_')
    {
      const char* s = section().form().validate(get(), msg);
      if (s) set(s);
    }
    else
    {
      const TString16 cmd(msg.get());      // Get command
      const word id = msg.get_int();       // Get destination group

      // Send the message to all fields with the given group
      for (word i = 0; i < section().fields(); i++)
      {
        TForm_item& des = section().field(i);
        if (des.in_group(id))
          send_message(cmd, des);
      }
    }  
  }

  return TRUE;
}


bool TForm_item::update()
{
  string_at(_x, _y, _prompt);
  do_message();
  return TRUE;
}

void TForm_item::print_on(TToken_string& row) const
{      
  row = class_name();
  row.add(_y);
  row.add(_x);
  
  const long fu = _group.first_one();
  if (fu > 0) row.add(fu); 
  else row.add(" ");
  row.add(key());
}

void TForm_item::print_on(TMask& m)
{
  m.set(F_CLASS, class_name());
  m.set(F_KEY, key());
  m.set(F_X, _x);
  m.set(F_Y, _y);
  m.set(F_PROMPT, _prompt);
  m.set(F_WIDTH, _width);
  m.set(F_HEIGHT, _height);
  _flag.print_on(m);
  
  for (int g = 1; g <= 24; g++)      
    m.set(F_GROUP+g, _group[g] ? "X" : " ");
}

void TForm_item::read_from(const TMask& m)
{
  _desc = m.get(F_KEY);
  _x = atoi(m.get(F_X));
  _y = atoi(m.get(F_Y));
  _prompt = m.get(F_PROMPT);
  _width = atoi(m.get(F_WIDTH));
  _height = atoi(m.get(F_HEIGHT));
  _flag.read_from(m);
  
  _group.reset();
  for (int g = 1; g <= 24; g++)      
    _group.set(g, m.get_bool(F_GROUP+g));
}


bool TForm_item::edit(TMask& m)
{
  m.reset();
  
  if (m.insert_mode()) m.enable(F_CLASS);
  else print_on(m);
  
  const bool dirty = (m.run() == K_ENTER) && m.dirty();

  if (m.insert_mode()) m.disable(F_CLASS);
  else if (dirty) read_from(m);
  
  return dirty;
}


///////////////////////////////////////////////////////////
// TForm_string
///////////////////////////////////////////////////////////

class TForm_string : public TForm_item
{
  TString _str, _picture;
  TArray _field;

protected:
  virtual const char* class_name() const { return "STRINGA"; }
  virtual void print_body(ostream& out) const;

  virtual void print_on(TMask& m);
  virtual void read_from(const TMask& m);

  virtual bool parse_item(TScanner&);
  virtual bool read();
  virtual bool update();

  const char* get() const;
  bool set(const char*);

  const TString& picture() const { return _picture; }

  TFieldref& field(int i) const { return (TFieldref&)_field[i]; }
  void put_paragraph(const char* s);

public:
  TForm_string(TPrint_section* section) : TForm_item(section) {}
  virtual ~TForm_string() {}
};

bool TForm_string::parse_item(TScanner& scanner)
{
  if (scanner.key() == "FI")
  {
    TFieldref* fr = new TFieldref(scanner.line(), 0);
    _field.add(fr);
    return TRUE;
  }

  if (scanner.key() == "PI")
  {
    _picture = scanner.string();
    return TRUE;
  }

  return TForm_item::parse_item(scanner);
}

void TForm_string::print_body(ostream& out) const
{
  TForm_item::print_body(out);
  if (_picture.not_empty())
    out << " PICTURE \"" << _picture << "\"" << endl;
  for (int i = 0; i < _field.items(); i++)
    out << " FIELD " << field(i) << endl;
}

void TForm_string::print_on(TMask& m)
{
  TForm_item::print_on(m);
  for (int i = 0; i < 2; i++) if (i < _field.items())
  {
    TString80 f; 
    f << field(i);
    m.set(i == 0 ? F_FIELD : F_FIELD2, f);
  }  
  
  m.set(F_PICTURE, _picture);
}

void TForm_string::read_from(const TMask& m)
{
  TForm_item::read_from(m);
  _picture = m.get(F_PICTURE);
  
  for (int i = 0; i < 2; i++)
  {
    const TString& f = m.get(i == 0 ? F_FIELD : F_FIELD2);
    if (f.not_empty())
      _field.add(new TFieldref(f, 0), i);
    else 
      _field.destroy(i);
  }  
}

bool TForm_string::set(const char* s)
{
  _str = s;
  return TRUE;
}

const char* TForm_string::get() const
{ return _str; }


// Se un campo e' abilitato ed ha almeno un riferimento su file leggilo 
bool TForm_string::read()
{
  bool ok = TRUE;

  if (enabled())
  {                    
    if (_field.items())
    {
      const char* s = "";
      const TRelation* r = section().form().relation(); 
      for (int i = 0; i < _field.items() && *s == '\0'; i++)
        s = field(i).read(r);
      set(s);
    }  
  } else ok = FALSE;

  return ok;
}

void TForm_string::put_paragraph(const char* s)
{
  if (hidden()) return;

  if (height() > 1)
  {
    TParagraph_string p(s, width());
    int i = _prompt.not_empty() ? 1 : 0;
    for (; (s = p.get()) != NULL && i < height(); i++)
      string_at(_x, _y+i, s);
  }
  else string_at(-1, _y, s);
}


bool TForm_string::update()
{
  if (read())
  {
    TForm_item::update();
    if (_picture.not_empty())
    {
      TString80 p;
      p.picture(_picture, get());
      put_paragraph(p);
    } 
    else 
      put_paragraph(get());
  }        
  
  return TRUE;
}

///////////////////////////////////////////////////////////
// TForm_number
///////////////////////////////////////////////////////////

class TForm_number : public TForm_string
{
protected:
  virtual const char* class_name() const { return "NUMERO"; }
  virtual bool parse_head(TScanner& scanner);
  virtual bool update();
  
  int decimals() const { return _height; }

public:
  TForm_number(TPrint_section* section) : TForm_string(section) {}
  virtual ~TForm_number() {}
};


bool TForm_number::parse_head(TScanner& scanner)
{
  _width = 0;
  _height = scanner.integer();
  return TRUE;
}


bool TForm_number::update()
{
  if (read())
  {              
    TForm_item::update();
    const char* s = get();
    real n(s);
    n.round(decimals());
    s = n.string(picture());
    string_at(-1, _y, s);
  }
  return TRUE;
}

///////////////////////////////////////////////////////////
// TForm_date
///////////////////////////////////////////////////////////

class TForm_date : public TForm_string
{
protected:
  virtual const char* class_name() const { return "DATA"; }
  virtual bool read();
  virtual bool set(const char*);
  bool set(const TDate& d);

public:
  TForm_date(TPrint_section* section);
  virtual ~TForm_date() {}
};


TForm_date::TForm_date(TPrint_section* section) 
: TForm_string(section)
{}

bool TForm_date::read()
{                        
  bool ok = TForm_string::read();
  if (ok && !get()[0] && automagic()) 
    set(main_app().printer().getdate());
  return ok;
}

bool TForm_date::set(const char* s)
{
  const TDate d(s);
  TForm_string::set(d.string((width() == 8) ? 2 : 4));
  return TRUE;
}

bool TForm_date::set(const TDate& d)
{
  TForm_string::set(d.string((width() == 8) ? 2 : 4));
  return TRUE;
}

///////////////////////////////////////////////////////////
// TForm_list
///////////////////////////////////////////////////////////

class TForm_list : public TForm_string
{
  TToken_string _codes;
  TToken_string _values;

protected:
  virtual const char* class_name() const { return "LISTA"; }
  virtual bool parse_item(TScanner& scanner); 
  virtual void print_on(TMask& m);
  virtual void read_from(const TMask& m);
  virtual void print_body(ostream& out) const;
  virtual bool update();

public:
  TForm_list(TPrint_section* section);
  virtual ~TForm_list() {}
};

TForm_list::TForm_list(TPrint_section* section)
: TForm_string(section)
{}

bool TForm_list::parse_item(TScanner& scanner)
{
  if (scanner.key() == "IT")
  {
    TToken_string s(scanner.string());
    _codes.add(s.get());
    _values.add(s.get());
    
    TString m(80);
    while (scanner.popkey() == "ME")
    {
      m = scanner.line();
      m.strip_spaces();
      message(_values.items()-1).add(m);
    }
    scanner.push();
    return TRUE;
  }

  return TForm_string::parse_item(scanner);
}

void TForm_list::print_on(TMask& m)
{       
  TForm_string::print_on(m);
  TSheet_field& s = (TSheet_field&)m.field(F_ITEMS);
  
  _codes.restart(); _values.restart(); 
  for (int i = 0; i < _codes.items(); i++)
  {
    TToken_string& row = s.row(i);
    row = _codes.get();
    row.add(_values.get());
    row.add(message(i));
  }
  s.force_update();
}

void TForm_list::read_from(const TMask& m)
{
  TForm_string::read_from(m);

  TSheet_field& s = (TSheet_field&)m.field(F_ITEMS);
  
  _codes = _values = ""; 
  for (int i = 0; i < s.items(); i++)
  {
    TToken_string& row = s.row(i);
    _codes.add(row.get(0));
    _values.add(row.get());
    message(i) = row.get();
  }
}

void TForm_list::print_body(ostream& out) const
{
  TForm_string::print_body(out);

  TToken_string& cod = (TToken_string&)_codes; // Trick to skip const
  TToken_string& val = (TToken_string&)_values;

  int i = 0;
  TString c(cod.get(0));
  TString v(val.get(0));

  for (; c[0]; c = cod.get(), v = val.get(), i++)
  {
    out << " ITEM \"" << c;
    if (v.not_empty()) out << '|' << v;
    out << '"';

    const char* m = ((TForm_list*)this)->message(i);
    if (*m) out << " MESSAGE " << m;

    out << endl;
  }
}


bool TForm_list::update()
{
  bool ok = TRUE;

  if (!read()) return ok;

  const TString& val =get();
  int pos = _codes.get_pos(val);
  if (pos < 0)
  {
    ok = yesno_box("Il campo '%s' non puo' valere '%s': continuare ugualmente",
                   (const char*)key(), (const char*)val);
    set(_codes.get(pos = 0));               
  }
  if (ok)
  {
    do_message(pos);

    if (!hidden())
    {
      const char* c = _values.get(pos);
      if (c == NULL) c = val;
      if (c) string_at(_x, _y, c);
    }
  }
  
  return ok;
}

///////////////////////////////////////////////////////////
// TForm_group
///////////////////////////////////////////////////////////

class TForm_group : public TForm_item
{
protected:
  virtual const char* class_name() const { return "GRUPPO"; }
  virtual bool parse_head(TScanner&) { return TRUE; }
  virtual bool update() { return TRUE; }

public:
  TForm_group(TPrint_section* section) : TForm_item(section) {};
  virtual ~TForm_group() {}
};


///////////////////////////////////////////////////////////
// TPrint_section
///////////////////////////////////////////////////////////

TMask* TPrint_section::_msk = NULL;

TPrint_section::TPrint_section(TForm* f) : _height(0), _x(0), _y(0), _form(f)
{}

TPrint_section::~TPrint_section()
{
  if (_msk) 
  {
    delete _msk;
    _msk = NULL;
  }  
}

const TPrint_section& TPrint_section::copy(const TPrint_section& ps)
{
  _item = ps._item;
  _height = ps._height;
  _x = ps._x;
  _y = ps._y;
  return ps;
}

TPrintrow& TPrint_section::row(int num)
{
  TPrintrow* pr = (TPrintrow*)objptr(num);
  if (!pr)
  {
    pr = new TPrintrow;
    add(pr, num);
  }
  return *pr;
}

void TPrint_section::offset(int& x, int& y) const
{
  if (x >= 0) x += _x;
  if (y >= 0) y += _y;
}


TForm_item* TPrint_section::parse_item(const TString& s)
{
  if (s == "ST")
    return new TForm_string(this);
  if (s == "NU")
    return new TForm_number(this);
  if (s == "DA")
    return new TForm_date(this);
  if (s == "LI")
    return new TForm_list(this);
  if (s == "GR")
    return new TForm_group(this);
  
  error_box("Campo di stampa non ammesso per la sezione di stampa: %s", (const char*)s);
  return NULL;
}


TForm_item* TPrint_section::parse_item(TScanner& scanner)
{
  return parse_item(scanner.key());
}


bool TPrint_section::parse(TScanner& scanner)
{
  _height = scanner.integer();
  _x = scanner.integer();
  _y = scanner.integer();

  while (scanner.popkey() != "EN")
  {
    TForm_item *fi = parse_item(scanner);
    if (fi == NULL) return FALSE;

    if (fi->parse(scanner))
      _item.add(fi);
    else
      return FALSE;
  }
  return TRUE;
}

// Azzera tutte le righe della sezione di stampa
void TPrint_section::reset()
{
  for (word i = 0; i < height(); i++)
    row(i).reset();
}

// Aggiorna tutti i campi e li stampa
bool TPrint_section::update()
{
  bool ok = TRUE;
  
  reset();
  for (word i = 0; i < fields(); i++)
  {
    const bool esito = field(i).update();
    if (!esito) ok = FALSE;
  }

  return ok;
}


bool TPrint_section::edit(const char* title, bool all)
{                   
  TMask m("ba2100s");  
  m.set_caption(title);
  
  m.set(F_HEIGHT, _height);
  m.set(F_X, _x);
  m.set(F_Y, _y);
  
  if (m.run() == K_ESC)
    return FALSE;

  bool dirty = m.dirty() != 0; 
  
  if (dirty)
  {
    _height = m.get_int(F_HEIGHT);
    _x = m.get_int(F_X);
    _y = m.get_int(F_Y);
  }
  
  TArray_sheet a(-1, -1, 0, 0, title, "Tipo@8|Riga@R|Col.@R|Gr.@R|Descrizione@40", all ? 0xE : 0x8);

  for (word i = 0; i < fields(); i++)
  {
    TToken_string* s = new TToken_string(128);
    field(i).print_on(*s);
    a.add(s);
  }

  KEY k;
  while ((k = a.run()) != K_ESC)
  {   
    i = (word)a.selected();
    
    if (_msk == NULL && (k == K_ENTER || k == K_INS))
    {
      _msk = new TMask("ba2100f");
      if (!all) _msk->disable(-7);
    }  
    
    switch(k)
    {
    case K_ENTER:
      _msk->set_mode(MODE_MOD);
      if (field(i).edit(*_msk))
      {
        field(i).print_on(a.row(i));
        dirty = TRUE;
      }
      break;
    case K_INS:
      if (all)
      {
        _msk->set_mode(MODE_INS);
        TForm_string dummy(this);
        if (dummy.edit(*_msk))
        {        
          const TString& c = _msk->get(F_CLASS).left(2);
          TForm_item* item = parse_item(c);
          if (item != NULL)
          {
            item->read_from(*_msk);
            _item.insert(item, i);
            
            TToken_string s(128); item->print_on(s);
            a.insert(s, i);
            dirty = TRUE;
          }  
        }
      } else error_box("L'inserimento e' disabilitato"); 
      break;
    case K_DEL:
      if (all && yesno_box("Confermare la cancellazione"))
      {
        _item.destroy(i, TRUE);
        a.destroy(i);
        dirty = TRUE;
      } else error_box("La cancellazione e' disabilitata"); 
      break;  
    default:
      break;
    }    
  }
  
  return dirty;
}

void TPrint_section::print_on(ostream& out) const
{
  out << ' ' << _height << ' ' << _x << ' ' << _y << endl << endl;
  for(word i = 0; i < fields(); i++)
    out << field(i);
}

///////////////////////////////////////////////////////////
// TGraphic_section
///////////////////////////////////////////////////////////

class TGraphic_section : public TPrint_section
{
  TString _back;                                        

protected:  
  TForm_item* parse_item(const TString& s);
  bool update();
  
public:
  void append(const char* s) { _back << s; }

  TGraphic_section(TForm* f) : TPrint_section(f) {}  
  virtual ~TGraphic_section() {}
};

class TForm_picture : public TForm_item
{
protected:
  virtual const char* class_name() const { return "FIGURA"; }
  virtual bool update();

public:
  TForm_picture(TGraphic_section* section) : TForm_item(section) {};
  virtual ~TForm_picture() {}
};

class TForm_line : public TForm_item
{
protected:
  virtual const char* class_name() const { return "LINEA"; }
  virtual bool update();

public:
  TForm_line(TGraphic_section* section) : TForm_item(section) {};
  virtual ~TForm_line() {}
};

bool TForm_picture::update()
{                        
  const bool ok = _prompt.not_empty();
  if (ok)
  {
    TString80 i;
    i << "i{" << _prompt << ',' << _x << ',' << _y << ',' 
      << (_x+width()-1) << ',' << (_y+height()-1) << '}';
    ((TGraphic_section&)section()).append(i);
  }  
  return ok;
}

bool TForm_line::update()
{                                        
  TString80 i;
  const int w = _prompt[0] == '@' ? 3 : 1; 
  i << 'W' << w << "l{" << _x << ',' << _y << ','
    << (_x+width()-1) << ',' << (_y+height()-1) << '}';
  
  ((TGraphic_section&)section()).append(i);
  return TRUE;
}


TForm_item* TGraphic_section::parse_item(const TString& s)
{
  if (s == "FI")
    return new TForm_picture(this);
  else if (s == "LI")
    return new TForm_line(this);

  error_box("Campo di stampa non ammesso per lo sfondo: %s", (const char*)s);
  return NULL;  
}

bool TGraphic_section::update()
{                       
  _back.cut(0);
  const bool ok = TPrint_section::update();
  main_app().printer().setbackground(_back);
  return ok;
}

///////////////////////////////////////////////////////////
// TForm
///////////////////////////////////////////////////////////

bool TForm::parse_use(TScanner& scanner)
{
  const int logicnum = scanner.integer();
  const char* tab = NULL;

  if (logicnum > 0)
    _relation = new TRelation(logicnum);
  else
  {
    tab = scanner.pop();
    _relation = new TRelation(tab);
  }

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

  _cursor = new TCursor(_relation, "", key);
  return TRUE;
}


bool TForm::parse_join(TScanner& scanner)
{
  TString16 j(scanner.pop());           // File or table

  int to = 0;
  if (scanner.popkey() == "TO")         // TO keyword
  {
    const char* n = scanner.pop();
    to = name2log(n);
  }
  else 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();
    }
  }
  if (exp.empty())
    yesnofatal_box("JOIN senza espressioni INTO");
  scanner.push();

  if (isdigit(j[0]))
    _relation->add(atoi(j), exp, key, to, alias);   // join file
  else
    _relation->add(j, exp, key, to, alias);         // join table

  return TRUE;
}


TPrint_section* TForm::exist(char s, pagetype t, bool create)
{
  TArray* a = NULL;
  switch (toupper(s))
  {
  case 'G':
    a = &_back; break;
  case 'F':
    a = &_foot; break;
  case 'H':
    a = &_head; break;
  default:
    a = &_body; break;
  }
  
  TPrint_section* sec = (TPrint_section*)a->objptr(t);
  if (sec == NULL && create) 
  {
    sec = (s == 'G') ? new TGraphic_section(this) : new TPrint_section(this);
    a->add(sec, t);
  }  
  
  return sec;
}


TPrint_section& TForm::section(char s, word p)
{              
  pagetype pos = odd_page;
  if (p == 0 && exist(s, last_page)) pos = last_page;
  if (p == 1 && exist(s, first_page)) pos = first_page;
  if (pos == odd_page && (p & 0x1) == 0 && exist(s, even_page)) pos = even_page;
  
  TPrint_section* sec = exist(s, pos, TRUE);
  return *sec;
}

word TForm::set_background(word p, bool u)
{
  word len = 0;
  
  if (u && _back.items()) 
  {
    TPrint_section& graph = section('G', p);
    graph.update();
    len = main_app().printer().formlen();
  }    
  
  return len;
}

word TForm::set_header(word p, bool u)
{           
  TPrinter& printer = main_app().printer();
  printer.resetheader();

  TPrint_section& head = section('H', p);
  
  if (u) head.update(); 
  else 
  {
    head.reset();
    printer.headerlen(head.height());
  }
  
  for (word j = 0; j < head.height(); j++)
    printer.setheaderline(j, head.row(j));
  
  return head.height();
}

word TForm::set_body(word p, bool u)
{
  TPrint_section& body = section('B', p);

  if (u) body.update(); 
  else body.reset();
  
  if (u) 
  {
    TPrinter& printer = main_app().printer();
    for (word j = 0; j < body.height(); j++)
      printer.print(body.row(j));
  }
  
  return body.height();
}

word TForm::set_footer(word p, bool u)
{
  TPrinter& printer = main_app().printer();
  printer.resetfooter();

  TPrint_section& foot = section('F', p);
  
  if (u) foot.update();
  else 
  {
    foot.reset();
    printer.footerlen(foot.height());
  }
  
  for (word j = 0; j < foot.height(); j++)
    printer.setfooterline(j, foot.row(j));

  return foot.height();
}

void TForm::header_handler(TPrinter& p)
{
  const word page = form().page(p);
  form().set_background(page, TRUE);
  form().set_header(page, TRUE);
  form().set_footer(page, FALSE);
}

void TForm::footer_handler(TPrinter& p)
{
  const word page = form().page(p);
  form().set_footer(page, TRUE); 
  if (page)
    form().set_header(page+1, FALSE);
}   


word TForm::page(const TPrinter& p) const
{
  return _lastpage ? 0 : p.getcurrentpage();
} 

long TForm::records() const
{             
  const long r = _cursor ? _cursor->items() : 0;
  return r;
}

// Stampa gli items dal from a to
// se to < 0 stampa fino alla fine del file

bool TForm::print(long from, long to)
{ 
  _form = this;                                    // Setta il form corrente
  
  TPrinter& printer = main_app().printer();        // Setta handlers
  printer.setheaderhandler(header_handler);
  printer.setfooterhandler(footer_handler);
  printer.formlen(height());
  const bool was_open = printer.isopen();

  _lastpage = FALSE;                               // non e' l'ultima pagina
  
  set_background(1, TRUE);

  if (!was_open && !printer.open())
    return FALSE;
  do_events();
  
  if (to < 0) to = records()-1;
  
  bool ok = TRUE;

  for (long i = from; i <= to && ok; i++)
  {
    if (from < 0) to = from;
    else if (_cursor) *_cursor = i;
    
    const word h = set_body(page(printer), FALSE);
    if (h > printer.rows_left())
      printer.formfeed();

    set_body(page(printer), TRUE);
  }
  
  if (i > records())
  {                
    if (exist('H', last_page) || exist('B', last_page) || exist('F', last_page))
    {       
      _lastpage = TRUE;
      set_background(0, TRUE);
      set_header(0, TRUE);
      set_body(0, TRUE);
      printer.formfeed();
    }
  }
  
  if (!was_open)
    printer.close();

  _form = NULL;                    // resetta handlers
  printer.setheaderhandler(NULL);
  printer.setfooterhandler(NULL);

  return ok;
}

void TForm::print_section(ostream& out, char s) const
{
  for (pagetype t = odd_page; t <= last_page; t = pagetype(t+1)) 
  {
    const TPrint_section* sec = ((TForm*)this)->exist(s, t);
    if (sec && sec->ok())
    {
      const char* name;
      switch (s)
      {
      case 'F': name = "FOOTER"; break;
              case 'G': name = "GRAPHICS"; break;
              case 'H': name = "HEADER"; break;
                default : name = "BODY"; break;
              }
      out << "SECTION " << name << ' ' << int(t);
      out << *sec;
      out << "END\n" << endl;
    }  
  }
} 

const char* TForm::validate(const char*, TToken_string&)
{ return NULL; }                                        

void TForm::print_on(ostream& out) const
{                   
  main_app().begin_wait();

  if (relation())
    out << *relation() << "\nEND" << endl;
  
  print_section(out, 'G');
  print_section(out, 'H');
  print_section(out, 'B');
  print_section(out, 'F');
  
  out << "END" << endl;
  
  main_app().end_wait();
}


word TForm::height()
{               
  word h = 0;
  
  if (_back.items() == 0)
  {
    if (_head.items())
      h += section('H', 1).height();
    if (_body.items())
      h += section('B', 1).height();
    if (_foot.items())
      h += section('F', 1).height();
  } 
  else 
    h = main_app().printer().formlen();    
  
  return h;
}


TForm::TForm(const char* name) 
: _name(name), _relation(NULL), _cursor(NULL)
{
  main_app().begin_wait();
  
  _name.ext("frm");
  TScanner scanner(_name);

  bool ok = TRUE;
  if (scanner.popkey() == "US")                         // Parse relation
  {
    ok = parse_use(scanner);
    while (ok && scanner.popkey() == "JO")
      ok = parse_join(scanner);
  } 
  else scanner.push();
  
  while (ok)
  {                         
    if (scanner.popkey() != "SE")                       // SECTION or END
      break;
    const char sec = scanner.popkey()[0];               // Section name (GR, HE, BO, FO)
    const pagetype p = (pagetype)scanner.integer();     // Section type (odd, even, first, last)   
    TPrint_section* ps = exist(sec, p, TRUE);           // Create section
    ok = ps->parse(scanner);                            // Parse section
  }         
  
  main_app().end_wait();
}


TForm::~TForm()
{
  if (_cursor)
  {
    delete _cursor;
    delete _relation;
  }
}