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

#include <xvt.h>
#include <checks.h>
#include <isamrpc.h>

#include <netsock.h> 

static TSocketClient* _client = NULL;

static CONNID _connection = 0;

bool rpc_Start()
{         
  bool ok = TRUE;
  if (_client == NULL)
  {
  	srand(clock()); 
    _client = new TSocketClient;
    if (!_client->IsOk())
    {
      delete _client;
      _client = NULL;
      ok = error_box("Errore di inizializzazione del socket client.");
    }  
  }
  return ok;
}

bool rpc_Stop()
{
  if (_client)
  {
    delete _client;
    _client = NULL;
    _connection = 0;
  }                 
  return TRUE;
}

bool rpc_Call(const char* cmd)
{ 
  CHECK(_connection, "Server not connected");
  bool ok = _client->Execute(_connection, cmd) != 0;
  return ok;
}

char* rpc_Request(const char* cmd, size_t& size, real& total)
{ 
  CHECK(_connection, "Server not connected");
  const clock_t start = clock();
  _client->Request(_connection, cmd);
  total = (clock() - start) / double(CLOCKS_PER_SEC);
  char* buff = (char*)_client->GetBuffer(size); 
  return buff;  
}

static TString _rpc_call(256);          
static TString _rpc_string;

inline bool BoolCall()
{
  CHECK(_connection, "Server not connected");
  bool yes;
  bool ok = _client->RequestBool(_connection, _rpc_call, yes) != 0; 
  if (!ok)
  {
#ifndef DBG  
    yesnofatal_box("RPC call failed: %s", (const char*)_rpc_call);
#endif    
    yes = FALSE;
  }
  return yes ? TRUE : FALSE;
}

inline long IntCall()
{
  CHECK(_connection, "Server not connected");
  long n;
  bool ok = _client->RequestInteger(_connection, _rpc_call, n) != 0;
  if (!ok)
  {
#ifndef DBG  
    yesnofatal_box("RPC call failed: %s", (const char*)_rpc_call);
#endif
    n = 0;
  }
  return n;
}

inline TString& StrCall()
{
  CHECK(_connection, "Server not connected");
  bool ok = _client->RequestString(_connection, _rpc_call, _rpc_string) != 0;
  if (!ok)
  {
#ifndef DBG  
    yesnofatal_box("RPC call failed: %s", (const char*)_rpc_call);
#endif
    _rpc_string.cut(0);
  }
  return _rpc_string;
}
      
inline bool BoolCallInt(const char* fn, long n)
{ 
  _rpc_call.format("%s(%ld)", fn, n);
  return BoolCall();  
}

inline long IntCallInt(const char* fn, long n)
{ 
  _rpc_call.format("%s(%ld)", fn, n);
  return IntCall();  
}

inline long IntCallIntInt(const char* fn, long n, long k)
{ 
  _rpc_call.format("%s(%ld,%ld)", fn, n, k);
  return IntCall();  
}

inline long IntCallIntIntInt(const char* fn, long n, long k, long f)
{ 
  _rpc_call.format("%s(%ld,%ld,%ld)", fn, n, k, f);
  return IntCall();  
}


inline long IntCallIntIntStr(const char* fn, long n, long k, const char* str)
{ 
  _rpc_call.format("%s(%ld,%ld,|%s|)", fn, n, k, str);
  return IntCall();  
}

inline long IntCallIntStr(const char* fn, long n, const char* str)
{ 
  _rpc_call.format("%s(%ld,|%s|)", fn, n, str);
  return IntCall();  
}

inline int IntCallIntStrInt(const char* fn, long n, const char* s, long f)
{ 
  _rpc_call.format("%s(%ld,|%s|,%ld)", fn, n, s, f);
  return (int)IntCall();  
}

inline long IntCallIntStrStr(const char* fn, long n, const char* str, const char* val)
{ 
  _rpc_call.format("%s(%ld,|%s|,|%s|)", fn, n, str, val);
  return IntCall();  
}

inline long IntCallStr(const char* fn, const char* str)
{ 
  _rpc_call.format("%s(%s)", fn, str);
  return IntCall();  
}


inline TString& StrCallIntInt(const char* fn, long n, long k)
{ 
  _rpc_call.format("%s(%ld,%ld)", fn, n, k);
  return StrCall();  
}

inline TString& StrCallIntStr(const char* fn, long n, const char* str)
{ 
  _rpc_call.format("%s(%ld,|%s|)", fn, n, str);
  return StrCall();  
}

bool rpc_DongleHasModule(word af)
{
  return BoolCallInt("DongleHasModule", af);
}

bool rpc_DongleModules(TBit_array& ba)
{                           
  size_t size;
  real time;                         
  
  word* buff = (word*)rpc_Request("DongleModules()", size, time);
  if (buff && size > 0)
  {
    ba.reset(); ba.set(0, TRUE);
    const int words = int(size/2);
    int module = 1;
    for (int i = 0; i < words; i++)
    {            
      for (int b = 0; b < 16; b++)
      {          
        if (buff[i] & (1 << b))
          ba.set(module, TRUE);
        module++;
      }
    }
  }
  return size > 0;
}

unsigned rpc_DongleNumber()
{          
  _rpc_call = "DongleNumber()";
  return (unsigned)IntCall();
}

unsigned rpc_DongleYear()
{          
  _rpc_call = "DongleYear()";
  return (unsigned)IntCall();
}

static unsigned int CreatePassword(TString& pass)
{
	const int BASE = 19;
	unsigned int num = 0;
	do 
	{
		num = 883*rand();
		while (num % 883 != 0) // Possible overflow
			num++;
		pass.cut(0);
		char str[2] = { '\0', '\0' };
		while (num > 0)
		{
			unsigned int k = num % BASE;
			if (k < 10)
				str[0] = '0'+k;
			else
				str[0] = 'A'+k-10;
			num /= BASE;
			pass.insert(str);
		}
	} while (pass.len() < 5);
	return num;
}

bool rpc_UserLogin(const char* server, const char* user, 
                   const char* dummy_password, const char* application)
{   
  if (_client == NULL)
  { 
    if (!rpc_Start())
      return FALSE;
  }
  
  const bool local = server == NULL || *server == '\0' || 
		                 xvt_str_compare_ignoring_case(server, "127.0.0.1") == 0 ||
										 xvt_str_compare_ignoring_case(server, "localhost") == 0;
	const TString name = local ? "locale" : server;
  
  if (_connection != 0)
    _client->RemoveConnection(_connection);

  _connection = _client->QueryConnection("1883", server);
   
  TString error;
  if (_connection)    
  {
		TString16 password;

		CreatePassword(password);

		const int session = xvt_sys_get_session_id();
    _rpc_call.format("UserLogin(%s,%s,%s,%d)", user, (const char*)password, application, session);

    long answer = 0;             
    bool connected = _client->RequestInteger(_connection, _rpc_call, answer) != 0; 
    if (connected)
    {   
			const bool logged = (answer == 1) || ((answer % 883 == 0) && (answer != 0));
      if (!logged)
      {
        connected = FALSE;
        error.format("La connessione di %s e' stata rifiutata dal Server %s", 
					           (const char*)user, (const char*)name);
      }
    }
    else                     
    {
      error.format("Impossibile connettersi al Server %s", (const char*)name);
    }
     
    if (!connected)
    {
      _client->RemoveConnection(_connection);
      _connection = 0;
    }
  }  
  else
  {                
		if (!local)
      error.format("Impossibile connettersi al Server %s", (const char*)name);
  }
  
  if (error.not_empty())
    error_box(error);
  
  return _connection != 0;
}

bool rpc_UserLogout(const char* appname)
{   
  if (_connection)                                
  {                  
		const int session = xvt_sys_get_session_id();
    _rpc_call.format("UserLogout(%s, %d, %s)", (const char*)user(), session, appname);
    rpc_Call(_rpc_call);
    _client->RemoveConnection(_connection);
    _connection = 0;
  }

  return TRUE;
}


bool http_get(const char* server, 
           const char* remote_file, 
           const char* local_file,
           const char* authorization)
{
  TSocketClient client;
  if (!client.IsOk())
    return error_box("Impossibile inizializzare il client HTTP");
   
  CONNID connection = client.QueryConnection("80", server);
  bool ok = connection != 0;
  if (ok)
  {
    ok = client.HttpGetFile(connection, remote_file, local_file, authorization) != 0;

    client.RemoveConnection(connection);

  }
   
  return ok;
}
   
bool http_dir(const char* server, const char* remote_dir, TString_array& list)
{
  TSocketClient client;
  if (!client.IsOk())
    return error_box("Impossibile inizializzare il client HTTP");
   
  unsigned long connection = client.QueryConnection("80", server);
  bool ok = connection != 0;
  if (ok)
  {
    ok = client.HttpGetDir(connection, remote_dir, list) != 0;
    client.RemoveConnection(connection);

  }
   
  return ok;
}

bool http_post(const char* server, const char* remote_file, 
               const char* local_file, const char* authorization,
               byte*& answer, size_t& length)

{
  TSocketClient client;
  if (!client.IsOk())
    return error_box("Impossibile inizializzare il client HTTP");
    
  unsigned long connection = client.QueryConnection("80", server);
  bool ok = connection != 0;
  if (ok)
  {
    ok = client.HttpPostFile(connection, remote_file, local_file, authorization) != 0;
    client.RemoveConnection(connection);
    answer = client.GetBuffer(length);

    if (!ok)
    {
      error_box("Impossibile spedire il file %s al server %s:\n%s", 
                local_file, server, answer);
    }
  }
  else
    return error_box("Impossibile connettersi al server %s", server);
  return ok;
}