#include <applicat.h>
#include <dongle.h>
#include <execp.h>
#include <mask.h>
#include <modaut.h>
#include <printer.h>
#include <relation.h>
#include <utility.h>

#include "ba0100a.h"
#include "ba0103.h"
#include "ba0100.h"

///////////////////////////////////////////////////////////
// Utility
///////////////////////////////////////////////////////////

static bool _installing = false;

void set_installing_flag()
{ _installing = true; }

bool installing()
{	return _installing; }

///////////////////////////////////////////////////////////
// Menu management
///////////////////////////////////////////////////////////

static int get_next_string(const char* s, int from, TString& str, char& brace)
{                 
  if (from < 0)
    return -1;

  char closing = '\0';
  int start = 0;
  
  for (int i = from; s[i]; i++)
  {
    if (s[i] == closing)
    {
      char* fine = (char*)(s + i);
      const char old = *fine;
      *fine = '\0';
      str = s + start;
      *fine = old;
      return i+1;
    }
    
    if (!closing)
    {
      switch(s[i])
      {                        
      case '\'':
      case '"' : closing = s[i]; break;
      case '<' : closing = '>' ; break;
      case '[' : closing = ']' ; break;
      default  : break;
      }
      if (closing)
      {
        start = i+1;
        brace = s[i];
      }
    }
  }
  
  return -1;
}

static int get_next_int(const char* s, int from, int& val)
{
  if (from < 0)
    return -1;

	const char* start = NULL;
	int i = 0;
	for (i = from; s[i]; i++)
	{
		if (start == NULL)
		{
			if (isdigit(s[i]))
			  start = s+i;
		}
		else
		{
		  if (s[i] == ',')
				break;
		}
	}
	if (start != NULL)
		val = atoi(start);
	
	return i;
}

///////////////////////////////////////////////////////////
// TTimed_image
///////////////////////////////////////////////////////////

class TTimed_image : public TImage
{              
  clock_t _last_time;

public:
  clock_t touch() { return _last_time = clock(); }
  clock_t last_time() const { return _last_time; }
  
  TTimed_image(const char* name) : TImage(name) { touch(); }
  virtual ~TTimed_image() { }
};

///////////////////////////////////////////////////////////
// Menu Item
///////////////////////////////////////////////////////////

bool TMenuitem::_fullscreen_always;

TMenuitem::TMenuitem(TSubmenu* sm) 
         : _submenu(sm), _icon(0), _exist(-1), _enabled(-1),
           _firm(false), _password(false), _reloadmenu(false), _fullscreen_43(false)
{ }

TMenuitem::TMenuitem(const TMenuitem& mi)
{
	_submenu = mi._submenu;
	_exist = mi._exist;
	_firm = mi._firm;
	_password = mi._password;
	_reloadmenu = mi._reloadmenu;
  _fullscreen_43 = mi._fullscreen_43;
  _icon = mi._icon;
  _enabled = mi._enabled;
  _caption = mi._caption;
  _action = mi._action;
  _type = mi._type;
}


TMenu& TMenuitem::menu() const
{ return _submenu->menu(); }

bool TMenuitem::create(const char* t)
{ 
  TString16 flags;            
  char brace;
  int start = 0;

  start = get_next_string(t, start, _caption, brace);
  start = get_next_string(t, start, _action,  _type);
  start = get_next_string(t, start, flags,   brace);
  start = get_next_int(t, start, _icon);
	_caption = dictionary_translate(_caption);
  
  for (int i = flags.len()-1; i >= 0; i--)
  {
    switch(toupper(flags[i]))
    {              
    case 'D': _exist = false; break;
    case 'F': _firm = true; break;
    case 'P': _password = true; break;
    case 'R': _reloadmenu = true; break;
    case 'S': _fullscreen_43 = true; break;
    default : break;
    }
  }

  bool visible = true;
  if (_type == '<')
  { 
    const word mod = dongle().module_name2code(_action.left(2));
    visible = dongle().shown(mod);
    if (visible)
    {
      if (_action.find('.') < 0)
        _action << ".men";
      TFilename n = _action;
	    if (n.custom_path())
		    visible = menu().read(n, _action);
	    else
		    _action.cut(0);
      _type = '[';
    }
    else
      _action.cut(0);
  }

  if (!visible || _action.blank())
    _exist = _enabled = false;

  // Controlla lo stato di aggiornamento
  if (_enabled && is_program())
    _enabled = !menu().is_dangerous(_action) && !menu().is_vanished(_action);

  return visible;
} 

int TMenuitem::icon() const
{ return _icon; }

TSubmenu* TMenuitem::child_submenu() const
{
  TSubmenu* sm = NULL;
  if (is_submenu())
  {
    sm = menu().find(_action);
    if (sm != NULL && sm->items() == 0)
      sm = NULL;
  }
  return sm;
}

bool TMenuitem::enabled() const
{
  if (_exist < 0)
  {
    bool yes = false;
    if (is_submenu())
    {
      yes = child_submenu() != NULL;
    }  
    else
    {
      yes = !menu().is_dangerous(_action);
      if (yes)
      {
        const int endname = _action.find(' ');
        const TFilename name(endname > 0 ? _action.left(endname) : _action);
        TFilename n = name;
        if (!n.custom_path())
        {
      	  const char* ext[] = { "exe", "pif", "com", "bat", NULL };
				  int e;
        	
      	  for (e = 0; ext[e]; e++)
      	  {
            n = name;	n.ext(ext[e]);
        	  if (n.custom_path())
         	    break;
      	  }                               
      	  yes = ext[e] != NULL;
        }
      }
    }
    ((TMenuitem*)this)->_exist = yes;
  }

  bool yes = _exist != 0;
  if (yes)
  {
    if (is_submenu())
    {
      const TSubmenu* mnu = child_submenu();
      yes = mnu != NULL && mnu->enabled();
    }
    else
    {
      if (_enabled < 0)
      {
        const TExternal_app app(_action);
        yes = app.can_run();
        ((TMenuitem*)this)->_enabled = yes;
      }
      yes = _enabled != 0;
    }
  }
  return yes;
}  

void TMenuitem::reset_permissions()
{
  _enabled = -1;
}
    
bool TMenuitem::perform_submenu() const
{   
  TSubmenu* mnu = child_submenu();
  bool ok = mnu != NULL && mnu->enabled();
  if (ok)
    ok = menu().jumpto(mnu);
  return ok;
}

// Alcuni programmi devono essere eseguiti singolarmente: ba1, ba2, cg6
bool TMenuitem::run_modal() const
{
  bool yes = true;
  if (submenu().menu().mask_mode() == 3)
    yes = _action.match("ba[12] -*", true) || _action.starts_with("cg6", true);
  return yes;
}

bool TMenuitem::run_fullscreen() const
{
  bool yes = _fullscreen_always; // Always full screen
  if (!yes && _fullscreen_43)    // Full screen on 4/3 monitor
  {
    RCT rct; xvt_vobj_get_outer_rect(SCREEN_WIN, &rct);
    const double ratio = double(rct.right) / double(rct.bottom);
    yes = ratio < 1.4;  // 4:3 = 1.333; 16:9 = 1.777; 16:10 = 1.600
  }
  return yes;
}

bool TMenuitem::perform_program() const
{    
  bool ok = true;
  
  if (_password) 
  {
    TMask mask("ba0100a");
    mask.disable(F_USER);
    mask.set(F_USER, "SERVIZIO");
    ok = false;
    if (mask.run() == K_ENTER)
    {                    
      const TDate oggi(TODAY);
      TString pwd; 
      pwd << dongle().administrator() << (oggi.month() + oggi.day());
      ok = pwd == mask.get(F_PASSWORD);
    }
    if (!ok) 
      error_box("Password di servizio errata!\nAccesso negato.");
  }

  if (_firm && main_app().get_firm() == 0)
  {
    // Forza ditta 1 in demo altrimenti chiedila all'utente
    const int cd = dongle().demo() ? 1 : 0; 
    ok = menu().set_firm(cd);  
  }

  if (ok)
  {
    TCurrency::force_cache_update();    // Chiude cache valute
    TExternal_app a(_action);

		const bool install_app = _action.starts_with("ba1 -6", true);
		if (install_app)	
		{
      user() = dongle().administrator(); // Divento temporaneamente amministratore
			a.run(true,3);	//e' una installazione -> applicazione in asincrono
			set_installing_flag();
		}
		else
    {
      if (run_modal() || run_fullscreen())
			{
        prefix().set(NULL);   // Chiude prefix
        a.run(false, 3);	    // e' un programma sincrono
        printer_destroy();    // Forza rilettura parametri della stampante
        prefix().set("DEF");  // Riapre prefix
      }
      else
        a.run(true, 3, false);	//e' un programma asincrono
    }
  }
  
  return ok;
}  

bool TMenuitem::perform() const
{
  bool ok = enabled();
  if (ok)
  {
    if (is_submenu())
      ok = perform_submenu();
    else                
      ok = perform_program();
  }    
  return ok;
}    

///////////////////////////////////////////////////////////
// Submenu
///////////////////////////////////////////////////////////

TSubmenu::TSubmenu(TMenu* menu, const char* name)
        : _menu(menu), _name(name), _items(12), 
          _exist(true), _firm(false), _enabled(-1)
{ }

void TSubmenu::read(TScanner& scanner)
{
  while (scanner.ok())
  {
    TString& line = scanner.line();
    if (line.empty())
      break;
    if (line[0] == '[')  
    {
      scanner.push();
      break;
    }
    
    char brace;
    if (line.starts_with("Caption", true))
		{
      get_next_string(line, 8, _caption, brace); 
			_caption = dictionary_translate(_caption);
		}	else  
    if (line.starts_with("Module", true))
    {
      const int equal = line.find('=');
      if (equal > 0)
      { 
	      TToken_string mod(line.after('='), ',');
        mod.strip_spaces();
        _modules = mod;
			}
    } else
    if (line.starts_with("Picture", true))
    {
      // Estrae solamente il nome del file immagine, elimina path ed estensione
      TFilename name;
      get_next_string(line, 8, name, brace); 
      xvt_fsys_parse_pathname(name, NULL, NULL, _picture.get_buffer(), NULL, NULL); 
    } else  
    if (line.starts_with("Flags", true))
    {                 
      TString16 flags;
      get_next_string(line, 6, flags, brace);
      if (flags.find('D') >= 0)
        _exist = false; 
      if (flags.find('F') >= 0)
        _firm = true; 
    } else
    if (line.starts_with("Item", true))
    {
      TMenuitem* item = new TMenuitem(this);
      if (item->create(line))
        add(item);
      else
        delete item;
    }
  }
}

int TSubmenu::find_string(const TString& str) const
{             
  bool found = false;

  TString caption;
  caption = _caption; caption.upper();
  if (caption.find(str) >= 0 || caption.match(str))
    found = true;
  
  for (int i = 0; i < items(); i++) 
  { 
    const TMenuitem& mi = item(i);
    caption = item(i).caption();
    caption.upper();
    const bool match = caption.find(str) >= 0 || caption.match(str);
    found = match && mi.is_program() && mi.enabled();
    if (found)  
      return i;
  }    
  
  return found ? 0 : -1;
}

int TSubmenu::find(const TMenuitem& it) const
{
  int i;
  for (i = items()-1; i >= 0; i--) 
  { 
    const TMenuitem& mi = item(i);
    if (mi.action() == it.action())
      break;
  }
  return i;
}

TImage& TSubmenu::image() const 
{ 
	return menu().image(picture()); 
}

bool TSubmenu::enabled() const 
{ 
  if (_enabled < 0)
  {
    bool yes = _exist != 0;
    if (yes)
    {
      if (_modules.full() && _modules != "0" && _modules != "ba")
      {
        yes = false;
        TToken_string& mod = (TToken_string&)_modules;
        FOR_EACH_TOKEN(mod, cod)
        {
          if (_menu->has_module(cod))
          {
            yes = true;
            break;
          }
        }
      }
      if (yes)
      {
        yes = false;
    	  for (int i = items()-1; i >= 0 && !yes; i--)
        {
	        const TMenuitem& mi = item(i);
          yes = mi.enabled();
        }
      }
    }

    ((TSubmenu*)this)->_enabled = yes;
  }
  return _enabled != 0; 
}

void TSubmenu::reset_permissions()
{
  if (_enabled >= 0)
  {
    _enabled = -1;
	  for (int i = items()-1; i >= 0; i--)
    {
	    TMenuitem& mi = item(i);
      mi.reset_permissions();
    }
  }
}

bool TSubmenu::perform(int i)
{
  bool ok = i >= 0 && i < items();
  if (ok)
    ok = item(i).perform();
  return ok;  
}

bool TMenu::read(const char* name, TString& root)
{   
  TString str(255);
  bool found = false;

  TFilename menuname = name;
  menuname.custom_path();
  TScanner scanner(menuname);
  while (scanner.ok())
  {
    const TString& line = found ? scanner.pop() : scanner.line();
    if (line.empty())
      break;
    
    char brace = '[';  
    get_next_string(line, 0, str, brace);  

    if (!found)
    {
      root = str;
      found = true;
    }  

    if (objptr(str) == NULL)
    {
      TSubmenu* mnu = new TSubmenu(this, str);
      mnu->read(scanner);
      add(str, mnu);
    }
    else
      break;   // Menu gia' caricato!
  }
  
  return found;
}

///////////////////////////////////////////////////////////
// Menu
///////////////////////////////////////////////////////////

bool TMenu::read(const char* name) 
{ 
  Tdninst dninst;
  dninst.find_killed(_vanished);

  TString root;
  bool ok = read(name, root); 
  if (ok && _current == NULL)
  {
    _default_menu = root;
    _current = find(root);
    _item = 0;
  }  
  return ok;
}

bool TMenu::set_firm(long firm) const
{
  if (firm <= 0)
  {
    TPointer_array codes;
    const int nditte = prefix().firms(codes);
    if (nditte == 1)
      firm = codes.get_long(0);
  }
  return main_app().set_firm(firm);
}

bool TMenu::jumpto(TSubmenu* next)
{
  if (next && next->disabled())
    next = NULL;

  if (next)
  {                        
    if (next->query_firm())
    {
      if (!set_firm(dongle().demo() ? 1 : 0))
        next = NULL;
    }
    if (next)
    {
      if (_stack.count() >= 32)
        _stack.destroy_base();
      _stack.push(_current->name());
      _current = next;
      _item = 0;
    }
  }  
  
  return next != NULL;  
}

bool TMenu::jumpto_root()
{
  TSubmenu* sm = find(_default_menu);
  return jumpto(sm);
}

TSubmenu& TMenu::pop()
{
  TSubmenu* sm = _current;
  if (!at_top())
  {                                       
    TString& name = (TString&)_stack.pop();
    sm = (TSubmenu*)objptr(name);
  }      
  if (sm)
  {
    _current = sm;
    _item = 0;
  }
  return *sm;
}

TSubmenu* TMenu::find_string(const TString& str)
{
  TString upstr(str);
  upstr.upper();

	if (_last_search != upstr)
	{
		_last_search = upstr;
		_ignore_list.destroy();
	}
  
  restart();
  
  TSubmenu * sm;
  for (sm = (TSubmenu*)get(); sm; sm = (TSubmenu*)get())
  {
    if (sm->enabled() && !_ignore_list.is_key(sm->name()))
    {                  
      const int item = sm->find_string(upstr);
      if (item >= 0)
      {
        jumpto(sm);
        _item = item;
        break;
      }  
    }  
  }

	if (sm != NULL)
		_ignore_list.add(sm->name());
  else
		_ignore_list.destroy();

  return sm;
} 

TSubmenu* TMenu::find_parent(const TSubmenu& sub)
{
  restart();
  TSubmenu* sm = NULL;
  for (sm = (TSubmenu*)get(); sm; sm = (TSubmenu*)get())
  {
		for (int i = sm->items()-1; i >= 0; i--)
		{
			const TMenuitem& mi = sm->item(i);
			if (mi.is_submenu() && mi.action().find(sub.name()) >= 0)
				break;
		}
	}
	return sm;
}

bool TMenu::perform()
{
  bool ok = _current != NULL;
  if (ok)
    ok = _current->perform(_item);
  return ok;
}

TImage& TMenu::image(const char* name)
{                 
  TTimed_image* img = (TTimed_image*)_images.objptr(name);
  if (img == NULL)
  { 
    TFilename realname;
    const char* ext[] = { "png", "gif", "jpg", "bmp", NULL };
    bool bFound = false;
    for (int i = 0; ext[i] && !bFound; i++)
    {
      realname = name;
      realname.ext(ext[i]);
      bFound = realname.custom_path();
    }
    if (bFound)
    {
      if (_images.items() == 0)
        _default_bmp = name;      // Store default bitmap name

      img = new TTimed_image(realname);  
      if (can_be_transparent(*img))
        img->convert_transparent_color(MASK_BACK_COLOR);
      _images.add(name, img);
    }  
    else
    {
      img = (TTimed_image*)&image(_default_bmp);
      if (img == NULL)
        fatal_box(FR("Impossibile trovare l'immagine %s"), (const char*)_default_bmp);
    }  
      
    if (_images.items() > 3)  
    {
      TString worst_bmp;
      clock_t worst_time = img->touch();  // Impedisco di cancellare la prossima
      _images.restart();
      for (THash_object* o = _images.get_hashobj(); o; o = _images.get_hashobj())
      {       
        if (o->key() != _default_bmp)         
        {
          TTimed_image& i = (TTimed_image&)o->obj();
          if (i.last_time() < worst_time)
          {
            worst_time = i.last_time();
            worst_bmp = o->key();
          }
        }  
      }
      _images.remove(worst_bmp);
    }  
  }
  img->touch();
  return *img;
}

void TMenu::reload_images()
{ 
	_images.destroy();

  // Reset permissions
  restart();
  for (TSubmenu* sm = (TSubmenu*)get(); sm; sm = (TSubmenu*)get())
    sm->reset_permissions();
}

bool TMenu::has_module(const char* mod)
{
  TDongle& donkey = dongle();
  const word module = donkey.module_name2code(mod);
  bool yes = module == BAAUT;
  if (!yes && donkey.active(module))
    yes = main_app().has_module(module) && !is_vanished(mod);
  return yes;
}

bool TMenu::is_dangerous(const char* mod)
{
  const char code[4] = { mod[0], mod[1], '\0', '\0' };
  return _dangerous.get_pos(code) >= 0;
}

bool TMenu::is_vanished(const TString& app)
{
  if (_vanished.empty() || app.starts_with("ba"))
    return false;

  if (_vanished.find('*') >= 0)
    return true;
  
  bool yes = _vanished.get_pos(app) >= 0;
  if (!yes && app[0] != '7' && isdigit(app[0]))
  {
    TString4 mod; mod << app[0] << app[1];
    const int cod = dongle().module_name2code(mod);
    mod = dongle().module_code2name(cod);
    yes = _vanished.get_pos(mod) >= 0;
  }
  return yes;
}


TMenu::TMenu() : _current(NULL), _item(0), _mask_mode(0)
{ }

TMenu::~TMenu()
{ }