#include <applicat.h>
#include <expr.h>
#include <golem.h>
#include <netsock.h>
#include <recarray.h>
#include <relation.h>
#include <scanner.h>
#include <utility.h>
#include <xml.h>

///////////////////////////////////////////////////////////
// TRecipient
///////////////////////////////////////////////////////////

class TRecipient : public TObject
{
  TString _address;
  TString _group;
  TString _expr;

public:
  const TString& address() const { return _address; }
  const TString& group() const { return _group; }
  void  add_expr(char op, const TString& expr);
  bool can_receive(const TRectype& rec) const;
  
  virtual bool ok() const 
  { return _address.not_empty() && _expr.not_empty(); }

  TRecipient(const TToken_string& str);
  virtual ~TRecipient() { }
};

TRecipient::TRecipient(const TToken_string& str)
{
  str.get(0, _address);
  str.get(1, _group);
}

void TRecipient::add_expr(char op, const TString& expr)
{
  if (_expr.not_empty())
    _expr << (op == 'A' ? "&&" : "||");

  if (expr.blank())
    _expr << 1;
  else
    _expr << '(' << expr << ')';
}

bool TRecipient::can_receive(const TRectype& rec) const
{
  TExpression e(_expr, _strexpr, TRUE);
	TString val;

  for (int v = 0; v < e.numvar(); v++)
  {
		const TFixed_string name(e.varname(v));

		val.cut(0);
    if (rec.exist(name))
      val = rec.get(name);
    else
		{
	
			const TFieldref f(name, 0);
			const int logicnum = table2logic(f.id());

			if (logicnum > 0 && logicnum != rec.num())
			{
				TToken_string & rel = prefix().get_relation(rec.num(), logicnum);
				if (rel.full())
				{
					TToken_string key;
					FOR_EACH_TOKEN(rel, tok)
						key.add(rec.get(tok));
					const TRectype & joined_rec = cache().get(logicnum, key);
					val = f.read(joined_rec);
				}
			}
			else
				val = f.read(rec);
		}
		e.setvar(name, val);
  }
  bool yes = e.as_bool();
  return yes;
}

///////////////////////////////////////////////////////////
// TPostman
///////////////////////////////////////////////////////////

class TPostman : public TObject
{
  long _firm;
  bool _recipients_ok;
  TArray _recipient;
  TAssoc_array _expr;

protected:
  void test_firm();
  
  TRecipient& recipient(int r) const 
  { return (TRecipient&)_recipient[r]; }

  void add_expr(const TString& addr,
                char op, const TString& expr);

  void load_filters();

public:
  bool can_dispatch_transaction(const TRectype& rec);
  bool dispatch_transaction(const TRectype& rec, 
                            const TFilename& name);

  TExpression* get_filter_expr(const char* flt);
  const char* get_filter(const char* flt);
  bool user_can(const char* flt, const TRelation* rel);

  TPostman();
  virtual ~TPostman() { }
};

void TPostman::test_firm()
{
  const long firm = prefix().get_codditta();
  if (firm != _firm)
  {
    _firm = firm;
    _recipients_ok = FALSE;
  }
}

void TPostman::add_expr(const TString& addr,
                        char op, const TString& expr)
{
  for (int r = _recipient.last(); r >= 0; r--)
  {
    TRecipient& rec = recipient(r);
    if (rec.address() == addr)
    {
      rec.add_expr(op, expr);
      break;
    }
    else
    {
      if (rec.group() == addr)
        rec.add_expr(op, expr);
    }
  }
}

bool TPostman::can_dispatch_transaction(const TRectype& rec)
{
  test_firm();
  if (!_recipients_ok)
  {
    _recipients_ok = TRUE;
    _recipient.destroy();
    
    TConfig cfg(CONFIG_DITTA, "MailTransactions");
    TAuto_token_string str; 
    TString addr, opr, expr;

    // Costruisce la lista dei destinatari
    for (int r = 0; cfg.exist("Recipient", r); r++)
    {
      str = cfg.get("Recipient", NULL, r);
      expand_sys_vars(str);
      TRecipient* rcp = new TRecipient(str);
      _recipient.add(rcp);
    }

    // Costruisce i filtri per i destinatari
    for (int f = 0; cfg.exist("Filter", f); f++)
    {
      str = cfg.get("Filter", NULL, f);
      expand_sys_vars(str);

      const int num = str.get_int(1);
      if (num != rec.num()) continue;

      str.get(0, addr);
      str.get(2, opr);
      str.get(3, expr);
      add_expr(addr, opr[0], expr);
    }

    // Elimina destinatari inutili
    for (int d = _recipient.last(); d >= 0; d--)
    {
      if (!recipient(d).ok())
        _recipient.destroy(d, TRUE);
    }
  }
  return _recipient.items() > 0;
}

static int write_xml(TConfig& cfg, void* jolly)
{
	TAssoc_array &vars = cfg.list_variables();
	TXmlItem &item = *(TXmlItem *) jolly;
	TToken_string tag(cfg.get_paragraph(), ',');
	const int logicnum = tag.get_int();
	const char * attr = logicnum > 0 ? "Field" : "Attr";
	int rownum = tag.get_int(); 
	
	if (logicnum > 0)
		tag = "Record";
	TXmlItem & child =item.AddChild(tag);
	if (logicnum > 0)
	{
		child.SetAttr("LogicNumber", logicnum);
		if (logicnum > LF_TAB)
			child.SetAttr("TableName", logic2table(logicnum));
		else
		{
			TString table;

			FOR_EACH_ASSOC_STRING(vars, hobj, key, val)
				if (logicnum <= LF_TAB && strcmp(key, "COD") == 0)
				{
					table = val;
					break;
				}
			child.SetAttr("TableName", table);
		}
		
		if (rownum > 0)
			child.SetAttr("RowNumber", rownum);
	}

	TString s;

	FOR_EACH_ASSOC_STRING(vars, hobj, key, val)
		if (val && *val)
		{
			s = val;
			if (s[0] == '"' && s.ends_with("\""))
			{
				s.rtrim(1);
				s.ltrim(1);
			}
			s.trim();
			if (TDate::isdate(s))
			{
				TDate date(s);

				child.AddSoapInt(attr, date.date2ansi()).SetAttr("Name", key);
			}
			else
			{
				if (real::is_natural(s))
					child.AddSoapInt(attr, atoi(s)).SetAttr("Name", key);
				else
					child.AddSoapString(attr, s).SetAttr("Name", key);
			}
		}
	return 0;
}

bool TPostman::dispatch_transaction(const TRectype& rec,
                                    const TFilename& name)
{
  bool ok = can_dispatch_transaction(rec);
  if (ok)
  {
		TToken_string dest;
		TToken_string file_dest;
		TToken_string soap_dest;
    TString last_error;

		for (int r = 0; r < _recipient.items(); r++)
		{
			const TRecipient& a = recipient(r);
			if (a.can_receive(rec))
			{
				const TString& addr = a.address();
				if (addr.starts_with("http"))  // Indirizzo http
					soap_dest.add(addr); else
			  if (addr.find('@') > 0)  // Indirizzo posta
					dest.add(addr);
				else
        {
          if (fexist(addr))
					  file_dest.add(addr);
          else
          {
            if (addr != last_error)
            {
              ok = error_box(FR("Non esiste la cartella di destinazione %s"), (const char*)addr);
              last_error = addr;
            }
          }
        }
			}
		}

		if (dest.items() > 0)
		{
			TMail_message msg(dest.get(0));
			for (const char* r = dest.get(1); r; r = dest.get())
				msg.add_copy_recipient(r);

			TString16 subject;
			switch (rec.num())
			{
			case LF_TAB:
			case LF_TABCOM:
			case LF_TABGEN:
				subject << rec.get("COD"); 
        break;
			default:
				subject << rec.num();
        break;
			}
			msg.set_subject(subject);

			TScanner trans(name);
			while (trans.good())
			{
				TString& line = trans.line();
				msg.add_line(line);
			}
			ok = msg.send(TRUE);
		}

		if (file_dest.items() > 0)
		{
      TString16 basename;
      const struct tm* tl = xvt_time_now();
      basename.format("%02d%02d%02d_%02d%02d%02d_0",
                      tl->tm_year%100, tl->tm_mon+1, tl->tm_mday,
                      tl->tm_hour, tl->tm_min, tl->tm_sec);

			FOR_EACH_TOKEN(file_dest, r)
      {
				TFilename output;
        int retry = 0;
        for (retry = 0; retry < 10; retry++) // Per ora tento solo 10 volte
        {
          output = r;
          output.add(basename);
          output << retry << ".ini";
          if (!output.exist())  // Ho generato un nome buono
            break;  
        }
        if (retry >= 10)
          ok = false;
        else
				  ok = fcopy(name, output);
      }
		}
		
		if (soap_dest.items() > 0)
		{
			TConfig trans(name);
			TXmlItem item;
			TSocketClient socket;
			char * buf = new char[1024 * 256];

#ifdef WIN32  
      ostrstream stream(buf, 1024 * 256);
#else
      ostringstream stream(buf);
#endif

			bool ok = true;
			
			item.SetTag("m:CampoTransaction");
			trans.for_each_paragraph(write_xml, &item);
			
			item.Write(stream, 2);
			stream << '\0';

#ifdef DBG
			TFilename name;
			char hostname[256];
			int len = strlen(buf);
			
			len += 79;
			xvt_sys_get_host_name(hostname, sizeof(hostname));

			name.temp();

			ofstream f(name);
			
			f << "POST / HTTP/1.1\n"
				<< "User-Agent: Campo\n"
				<< "Host: " << hostname << "\n"
				<< "Content-Type: text/xml; charset=utf-8\n"
				<< "Content-length: " << len << "\n"
				<< "SOAPAction: \"/\"\r\n\r\n"
				<< "<SOAP-ENV:Envelope>\n<SOAP-ENV:Body>\r\n";

			item.Write(f, 2);

			f << "\n</SOAP-ENV:Body>\n</SOAP-ENV:Envelope>\r\n\r\n";
#endif

			FOR_EACH_TOKEN(soap_dest, r)
			{
				CONNID id = socket.QueryConnection("", r);
				socket.HttpSoap(id, buf);
			}
		}
  }  
  return ok;
}

void TPostman::load_filters()
{
  TRecord_cache users(LF_USER);
  
  TToken_string perm(4096, '\n');
  TAuto_token_string row(80);

  // Costruisce il nome dell'applicazione principale
  TFilename app(main_app().argv(0));
  app.ext("");
  app = app.name();

  for (int a = 1; a < main_app().argc(); a++)
  {
    row = main_app().argv(a);
    if (row[0] != '/') 
      app << ' ' << row;
    else
      break;
  }

  // Stringhe delle espressioni i filtro
  TAssoc_array expr;
  
  // Scandisce l'albero degli utenti/gruppi
  for (TString16 u = user(); u.not_empty() && !users.already_loaded(u); )
  {                    
    const TRectype& urec = users.get(u);       
    
    // Test di validita'del record da eseguire solo la prima volta
    if (u == user() && !urec.exist("PERMISSION")) 
      break;
    perm = urec.get("PERMISSION");  // Permessi del nodo corrente
    if (!perm.blank())
    {
      FOR_EACH_TOKEN(perm, tok)
      {
        row = tok;
        const TString80 appmod = row.get(0);
        const bool is_mod = appmod.len() == 2;
        // Il programma oppure il modulo corrispondono
        if ((is_mod && app.compare(appmod, 2, TRUE) == 0) ||
             app.compare(appmod, -1, TRUE) == 0) 
        { 
          TString80 key = row.get(2); key.trim(); // Tipo di filtro 
          row = row.get(); row.trim();  // Espressione di filtro 
          if (key.not_empty() && row.not_empty() && row != "1")
          {
            key.upper();
            TString* str = (TString*)expr.objptr(key);
            if (str == NULL)            
            {
              str = new TString(row);   // Crea una nuova stringa
              expr.add(key, str);
            }
            else
            {                           // Somma alla stringa precendente 
              if (*str != "0")          // Se sono FALSE lasciami stare
              {
                if (row != "0")         // Se aggiungo qualcosa di non ovvio
                {
                  str->insert("(", 0);
                  *str << ")AND(";           
                  *str << row << ')';
                }
                else
                  *str = "0";              
              }
            }
          }
        }
      }
    }
    u = urec.get("GROUPNAME");
  } 

  // Trasforma le stringhe in espressioni     
  FOR_EACH_ASSOC_STRING(expr, hash, key, str)
  {
    TExpression* e = new TExpression(str, _strexpr, TRUE);
    _expr.add(key, e);
  }

  // Inserisce un elemento fasullo per segnalare l'avvenuta lettura
  if (_expr.items() == 0)  
    _expr.add("", NULL);  
}

TExpression* TPostman::get_filter_expr(const char* flt)
{
  if (_expr.items() == 0)
    load_filters();
  TString80 f(flt); f.upper();
  TExpression* e = (TExpression*)_expr.objptr(f);
  return e;
}

const char* TPostman::get_filter(const char* flt)
{
  TExpression* e = get_filter_expr(flt);
  return e ? e->string() : NULL;
}

bool TPostman::user_can(const char* flt, const TRelation* rel)
{
  bool yes_he_can = TRUE;
  TExpression* e = get_filter_expr(flt);
  if (e != NULL)
  {
    if (rel != NULL)
    {
      for (int i = e->numvar()-1; i >= 0; i--)
      {
        const TString& name = e->varname(i);
        const TFieldref ref(name, 0);
        const TString& val = ref.read(*rel);
        e->setvar(name, val);
      }
      yes_he_can = e->as_bool();
    }
    else
    {
      if (e->numvar() == 0)
        yes_he_can = e->as_bool();
    }
  }
  return yes_he_can;
}

TPostman::TPostman() : _firm(-1), _recipients_ok(FALSE)
{
}

TPostman& postman()
{
  static TPostman* _postman = NULL;
  if (_postman == NULL) 
    _postman = new TPostman;
  return *_postman;
}

///////////////////////////////////////////////////////////
// Public interface
///////////////////////////////////////////////////////////

bool can_dispatch_transaction(const TRectype& rec)
{
  return postman().can_dispatch_transaction(rec);
}

bool dispatch_transaction(const TRectype& rec, const TFilename& name)
{
  return postman().dispatch_transaction(rec, name);
}

const char* get_user_filter(const char* flt)
{
  return postman().get_filter(flt);
}

const char* get_user_read_filter()
{
  return get_user_filter("Lettura");
}

const char* get_user_write_filter()
{
  return get_user_filter("Scrittura");
}

bool user_can_read(const TRelation* rel)
{
  return postman().user_can("Lettura", rel);
}

bool user_can_write(const TRelation* rel)
{
  return user_can_read(rel) && postman().user_can("Scrittura", rel);
}

bool user_can_delete(const TRelation* rel)
{
  return user_can_write(rel) && postman().user_can("Eliminazione", rel);
}

bool user_can_do(const char* azione, const TRelation* rel)
{
  return postman().user_can(azione, rel);
}