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

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

#include <bagn003.h>

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

struct TForm_flags : public TObject
{
  bool automagic : 1;
  bool disabled  : 1;
  bool hidden    : 1;

protected:
  void print_on(ostream& out) const;

public:
  TForm_flags();

  void to_mask(TMask& m) const;
  void from_mask(const TMask& m);

  bool update(const char* s);
};

TForm_flags::TForm_flags()
{
  hidden = automagic = disabled = FALSE;
}

bool TForm_flags::update(const char* s)
{
  for (; *s; s++) switch(toupper(*s))
  {
  case 'A': automagic = TRUE; break;
          case 'D': disabled = TRUE; break;
          case 'H': hidden = TRUE; break;
            default : error_box("Unknown form item flag '%c'", *s); break;
          }
  return TRUE;
}

void TForm_flags::print_on(ostream& out) const
{
  TString s(4);

  if (automagic) s << "A";
  if (disabled) s << "D";
  if (hidden) s << "H";

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

void TForm_flags::to_mask(TMask& m) const
{
  m.set(F_DISABLED, disabled ? "X" : " ");
  m.set(F_HIDDEN, hidden ? "X" : " ");
  m.set(F_AUTOMAGIC, automagic ? "X" : " ");
}

void TForm_flags::from_mask(const TMask& m)
{
  hidden = m.get_bool(F_HIDDEN);
  disabled = 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;
  TArray _message;

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

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

  virtual void to_mask(TMask& m) const;
  virtual void from_mask(const TMask& m);

  int width() const { return _width; }
  int heigth() const { return _heigth; }

  bool hidden() const { return _flag.hidden; }
  bool disabled() const { return _flag.disabled; }
  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();

  bool edit(TMaskmode mode);

  TPrint_section* section() const { return _section; }

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

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

  TForm_item(TPrint_section* section);
};


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

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

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

  print_body(out);

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

void TForm_item::print_body(ostream& out) const
{
  if (_y >= 0)
    out << " PROMPT " << _x << ' ' << _y << " \"" << _prompt << "\"\n";

  if (_desc.not_empty())
    out << " KEY \"" << _desc << "\"\n";

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

  out << _flag;

  if (_message.items() == 1)
  {
    const char* m = (TToken_string&)_message[0];
    out << " MESSAGE " << m << 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")
  {
    TString 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;
  }

  error_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 = error_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.disabled = !on;
  if (on) show();
  else hide();
}


void TForm_item::string_at(int x, int y, const char* s)
{
  if (hidden()) return;

  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') ? get() : "1.0");
    real r(des->get());
    r += n;
    des->set(r.string());
  } else
    if (cmd == "COPY")
    {
      des->set(get());
    } else
      if (cmd == "APPEND")
      {
        TString val(80);
        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 == "SHOW")
              {
                des->show();
              } else
                if (cmd == "RESET")
                {
                  des->set("");
                } else
                  if (cmd[0] == '"')
                  {
                    TString 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)
{
  if (message(num).empty()) return FALSE;

  TToken_string msg(16, ',');
  TString cmd(8);
  const char* m;

  message(num).restart();
  while ((m = message(num).get()) != NULL)
  {
    msg = m;
    cmd = msg.get();                     // Get command

    TFixed_string str(msg.get());
    const word id = atoi(str);    // Get destination id

    // Send the message to all fields with the given group
    for (int i = 0; i < section()->items(); i++)
    {
      TForm_item* des = section()->item(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::to_row(TToken_string& row) const
{
  row = class_name();
  row.add(key());
}

void TForm_item::to_mask(TMask& m) const
{
  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);
  _flag.to_mask(m);

  TString g;
  g << _group;
  m.set(F_GROUP, g);
}

void TForm_item::from_mask(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));

  TString flag;
  if (m.get(F_AUTOMAGIC) == "X") flag << "A";
  if (m.get(F_HIDDEN) == "X") flag << "H";
  _flag.update(flag);
}


bool TForm_item::edit(TMaskmode mode)
{
  TMask m("bagn003", mode);
  if (mode == MODE_INS)
  {
    m.enable(0);
    m.show();
  }

  to_mask(m);
  const bool dirty = (m.run() == K_ENTER) && m.dirty();
  if (dirty) from_mask(m);

  return dirty;
}


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

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

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

  virtual void to_mask(TMask& m) const;
  virtual void from_mask(const TMask& m);

  virtual bool parse_item(TScanner&);
  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.objptr(i); }
  void put_paragraph(const char* s);

  bool read();

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

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::to_mask(TMask& m) const
{
  TForm_item::to_mask(m);
  TString f(24); f << *field(0);
  m.set(F_FIELD, f);
  m.set(F_PICTURE, _picture);
}

void TForm_string::from_mask(const TMask& m)
{
  TForm_item::from_mask(m);
  _picture = m.get(F_PICTURE);
  *field(0) = m.get(F_FIELD);
}

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

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

bool TForm_string::read()
{
  if (_field.items() && !disabled())
  {
    const char* s = "";
    for (int i = 0; i < _field.items(); i++)
    {
      s = field(i)->read(section()->form()->relation());
      if (*s) break;
    }
    set(s);
    return TRUE;
  }

  return FALSE;
}

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

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


bool TForm_string::update()
{
  TForm_item::update();
  if (read())
  {
    if (_picture.not_empty())
    {
      TString 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 "NUMBER"; }
  virtual bool parse_head(TScanner& scanner);
  virtual bool update();

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


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


bool TForm_number::update()
{
  TForm_item::update();
  if (read())
  {
    real n(get());
    n.round(heigth());
    const char* s = n.string(picture());
    put_paragraph(s);
  }
  return TRUE;
}

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

class TForm_date : public TForm_string
{
protected:
  virtual const char* class_name() const { return "DATE"; }
  virtual bool set(const char*);

public:
  TForm_date(TPrint_section* section);
};


TForm_date::TForm_date(TPrint_section* section)
: TForm_string(section)
{
  if (automagic())
  {
    TDate oggi(TODAY);
    set(oggi.string());
  }
}


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

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

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

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

public:
  TForm_list(TPrint_section* section);
};

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());
    while (scanner.popkey() == "ME")
    {
      TString 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_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;

  int pos = _codes.get_pos(get());
  if (pos < 0)
  {
    TString k(_codes.get(0));
    ok = error_box("Il campo '%s' non puo' valere '%s': assumo '%s'",
                   (const char*)key(), get(), (const char*)k);
    set(k);
    pos = 0;
  }

  do_message(pos);

  if (!hidden())
  {
    if (_prompt.not_empty())
    {
      int found = -1;
      for (int i = 0; found < pos && _prompt[i]; i++)
        if (_prompt[i] != ' ') found++;
      if (found == pos)
      {
        char c[2] = { _prompt[i], '\0' };
        string_at(_x+i, _y, c);
      }
      else error_box("Non trovo l'elemento %d in '%s'", (const char*)_prompt);
    }
    else
    {
      const char* c = _values.get(pos);
      if (c == NULL) c = get();
      if (c) string_at(_x, _y, c);
    }
  }

  return ok;
}


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

void TPrint_section::init(const char* name, TForm* f)
{
  _name = name;
  _form = f;
}

TPrint_section::TPrint_section(const char* name, TForm* f)
: _heigth(0)
{
  init(name, f);
}

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

TForm_item* TPrint_section::parse_item(TScanner& scanner)
{
  if (scanner.key() == "ST")
    return new TForm_string(this);
  if (scanner.key() == "NU")
    return new TForm_number(this);
  if (scanner.key() == "DA")
    return new TForm_date(this);
  if (scanner.key() == "LI")
    return new TForm_list(this);

  error_box("Elemento sconosciuto nella sezione %s: %s",
            (const char*)_name, (const char*)scanner.token());
  return NULL;
}

bool TPrint_section::parse(TScanner& scanner)
{
  _heigth = scanner.integer();

  set_cursor(TASK_WIN, CURSOR_WAIT);

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

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

  set_cursor(TASK_WIN, CURSOR_ARROW);

  return TRUE;
}

bool TPrint_section::update()
{
  destroy();
  const int last = _item.items();
  bool ok = TRUE;
  for (int i = 0; i < last; i++)
  {
    bool esito = ((TForm_item&)_item[i]).update();
    if (!esito) ok = FALSE;
  }
  i = items();
  if (i < _heigth) row(_heigth-1);
  else if (i > _heigth)
    ok = warning_box("Sezione %s troppo lunga: %d > %d",
                     (const char*)_name, i, _heigth);
  return ok;
}

bool TPrint_section::edit(int mode)
{
  TString title(40);
  title = "Campi della sezione "; title << _name;

  TArray_sheet a(-1, -1, 0, 0, title, "Tipo@6|Descrizione@40", 0x8);

  for (int i = 0; i < items(); i++)
  {
    TToken_string s(64);
    item(i)->to_row(s);
    a.add(s);
  }

  bool dirty = FALSE;
  while (a.run() == K_ENTER)
  {
    i = (int)a.selected();
    const bool spork = item(i)->edit((TMaskmode)mode);
    if (spork)
    {
      dirty = TRUE;
      item(i)->to_row(a.row(i));
    }
  }
  return dirty;
}

void TPrint_section::print_on(ostream& out) const
{
  out << "\nPAGE " << _name << ' ' << _heigth << '\n' << endl;
  for(int i = 0; i < items(); i++)
    out << *item(i);
}

///////////////////////////////////////////////////////////
// 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();
    if (strlen(tab) > 4)
      return error_box("'%s' non e' una tabella valida: %d", tab);
    _relation = new TRelation(tab);
  }

  int key = 1;
  if (scanner.popkey() == "KE")
  {
    key = scanner.integer();
    if (key < 1)
    {
      error_box("Chiave non valida '%s': uso 1",
                (const char*)scanner.token());
      key = 1;
    }
  }
  else scanner.push();

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


bool TForm::parse_join(TScanner& scanner)
{
  TString 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())
      error_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;
}


bool TForm::set_header()
{
  TPrinter& printer = MainApp()->printer();
  printer.resetheader();

  const bool ok = _head.update();

  int last = _head.last();
  for (int j = 0; j <= last; j++)
    printer.setheaderline(j, _head.row(j));

  return ok;
}


bool TForm::set_footer()
{
  TPrinter& printer = MainApp()->printer();
  printer.resetfooter();

  const bool ok = _foot.update();

  int last = _foot.last();
  for (int j = 0; j <= last; j++)
    printer.setfooterline(j, _foot.row(j));

  return ok;
}

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

bool TForm::print(long from, long to)
{
  TPrinter& printer = MainApp()->printer();
  const bool was_open = printer.isopen();

  if (!was_open && !printer.open())
    return FALSE;

  if (to < 0) to = _cursor->items()-1;

  bool ok = set_header() && set_footer();

  for (long i = from; i <= to && ok; i++)
  {
    if (from < 0) to = from;
    else *_cursor = i;

    ok &= _body.update();
    if (!ok) ok = yesno_box("Stampo ugualmente");
    if (!ok) break;

    const word last = _body.last()+1;
    if (last < printer.rows() && last > printer.rows_left())
      ok = printer.formfeed();
    for (word j = 0; j < last && ok; j++)
      ok = printer.print(_body.row(j));
  }

  if (!was_open)
    printer.close();

  return ok;
}


void TForm::print_on(ostream& out) const
{
  out << *_cursor->relation() << endl;
  if (_head.items()) out << _head;
  if (_body.items()) out << _body;
  if (_foot.items()) out << _foot;
  out   << "ENDPAGE" << endl;
}


bool TForm::edit(int mode, int section)
{
  bool dirty = FALSE;

  switch (section)
  {
  case  0: dirty = _head.edit(mode); break;
         case  2: dirty = _foot.edit(mode); break;
         default: dirty = _body.edit(mode); break;
         }

  if (dirty && yesno_box("Salvare le modifiche?"))
  {
    set_cursor(TASK_WIN, CURSOR_WAIT);
    TFilename bak(_name);  bak.ext("bak");
    rename(_name, bak);
    ofstream out(_name);
    print_on(out);
    set_cursor(TASK_WIN, CURSOR_ARROW);
  }

  return dirty;
}



TForm::TForm(const char* name) : _name(name)
{
  _name.ext("frm");
  TScanner scanner(_name);

  bool ok = FALSE;
  if (scanner.popkey() == "US")
    ok = parse_use(scanner);

  CHECKS(ok, "Missing USE in form: ", name);

  while (ok && scanner.popkey() == "JO")
    ok = parse_join(scanner);
  scanner.push();

  _head.init("HEADER", this);
  _body.init("BODY", this);
  _foot.init("FOOTER", this);

  while (scanner.popkey() == "PA")
  {
    scanner.pop();
    if (scanner.token() == "HEADER") _head.parse(scanner); else
      if (scanner.token() == "BODY")   _body.parse(scanner); else
        if (scanner.token() == "FOOTER") _foot.parse(scanner);
  }
}

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