#include <signal.h>
#include <string>
#include "riscos.h"
#include "armswis.h"

#include "module-utility.h"
#include "module-wimp.h"
#include "module-fpemulator.h"
#include "module-taskwindow.h"
#include "module-territory.h"
#include "module-ddeutils.h"
#include "module-sharedunixlibrary.h"
#include "module-mimemap.h"
#include "module-iconv.h"
#include "module-resourcefs.h"
//#include "module-sharedclibrary.h"
#include "module-internet.h"

// Instantiate and initialise the RISC OS world
RISCOS::RISCOS (void)
{
  modlist.clear ();
  modlist.push_back (new OS ());
  modlist.push_back (new ResourceFS ());
  modlist.push_back (new Wimp ());
  modlist.push_back (new FPEmulator ());
  modlist.push_back (new TaskWindow ());
  modlist.push_back (new Territory ());
  modlist.push_back (new DDEUtils ());
  //modlist.push_back (new SharedUnixLibrary ());
  modlist.push_back (new MimeMap ());
  modlist.push_back (new Iconv ());
  modlist.push_back (new Internet());
  //modlist.push_back (new SharedCLibrary ());
  //modlist.push_back (new SCL ());
}

RISCOS::~RISCOS ()
{
  if (modlist.size ())
    {
      for (modules_t::iterator x = modlist.begin (); x != modlist.end (); x++)
	delete (*x);
      modlist.clear ();
    }
}

void RISCOS::RegisterModule (const struct native_module &m)
{
  native_modlist.push_back (m);
}

// Given a pointer 'ptr' to a module that has previously been loaded
// into RISC OS memory, extract whatever internal data we require
// and store on an internal linked list for future fast lookups.
void RISCOS::InitModule (uint32_t *ptr, uint32_t length)
{
  struct native_module m;
  memset (&m, 0, sizeof (struct native_module));
  m.base = (void *) ptr;
  m.start_code = ptr[0];
  m.init_code = ptr[1];
  m.fini_code = ptr[2];
  m.service_call_handler = ptr[3];
  m.title_string = ptr[4];
  m.help_string = ptr[5];
  m.command_table = ptr[6];

  // RISC OS deems these optional by virtue of containing
  // invalid data.
  if (ptr[7] < 0x100000)
    m.swi_chunk_base = ptr[7];
  if (ptr[8] < length)
    m.swi_handler_code = ptr[8];
  if (ptr[9] < length)
    m.swi_decode_table = ptr[9];
  if (ptr[10] < length)
    m.swi_decode_code = ptr[10];

  // Allocate a module private word
  uint32_t *pw = (uint32_t *) memc->module_private_word;
  int x = 0;
  while (x < 256 && pw[x] != 0xffffffff)
    x++;

  if (x == 256)
    {
      printf ("InitModule: couldn't assign a private word\n");
    }
  else
    {
      m.private_word = pw + x;
      *m.private_word = 0;
    }

  //  printf ("InitModule: base=%08x, start=%08x, init=%08x, fini=%08x, private=%08x\n",
  //	  m.base, m.start_code, m.init_code, m.fini_code, m.private_word);

  native_modlist.push_back (m);
}

// Start executing instructions at the address determined from the
// module header pointed to by 'base_address'.
// Return true if module call was handled.
bool RISCOS::ModuleCall (CPUARMState *env,
			 uint32_t *base_address, uint32_t offset)
{
  // Verify module exists.
  nmodule_t_iter x = native_modlist.begin ();
  while (x != native_modlist.end ())
    {
      if (x->base == base_address)
	break;
      x++;
    }

  // No match, so fail.
  if (x == native_modlist.end ())
    return false;

  // Module matched, setup SVC stack and jump to code
  uint32_t r12, sp, lr, pc;
  r12 = env->regs[12];
  sp = env->regs[13];
  lr = env->regs[14];
  pc = env->regs[15];
  env->regs[12] = (uint32_t) x->private_word;
  env->regs[13] = (uint32_t) memc->svc_stack_top;
  env->regs[14] = (uint32_t) memc->swi_code_return;

  // Add module base address as the offsets in the module header
  // are relative.
  uint32_t code_ptr = (uint32_t) x->base;

  if (offset == 0) { code_ptr += x->start_code; }
  else if (offset == 1) { code_ptr += x->init_code; }
  else if (offset == 2) { code_ptr += x->fini_code; }
  else if (offset == 3) { code_ptr += x->service_call_handler; }
  else if (offset == 4) { code_ptr += x->swi_handler_code; }
  else
    {
      printf ("ModuleCall: unrecognised offset %d\n", offset);
    }

  if (code_ptr == (uint32_t) x->base)
    {
      printf ("ModuleCall: module contains no routing for offset %d\n",
	      offset);
      return true;
    }

  env->regs[15] = (uint32_t) code_ptr;

  //printf ("ModuleCall: starting cpu_loop, offset=%d, lr=%08x, addr=%08x\n",
  //	  offset, env->regs[14], env->regs[15]);
  // Normally this is an infinite loop, but the swi_code_return routine
  // will ensure the function returns.
  cpu_loop (env);

  //printf ("ModuleCall: finished cpu_loop\n");
  // Restore USR stack
  env->regs[12] = r12;
  env->regs[13] = sp;
  env->regs[14] = lr;
  env->regs[15] = pc;
  return true;
}

void RISCOS::LoadModule (CPUARMState *env, const char *module_name)
{
  //printf ("LoadModule: load '%s'\n", module_name);
  env->regs[0] = 1;
  env->regs[1] = (uint32_t) module_name;
  SWI (env, 0x1e);
  //printf ("LoadModule: '%s' loaded\n", module_name);
}

void RISCOS::LoadModules (CPUARMState *env)
{
  LoadModule (env, "/usr/local/share/qemu/riscos/SharedULib,ffa");
  //LoadModule (env, "ResourceFS,ffa");
  //LoadModule (env, "Messages,ffa");
  //LoadModule (env, "MessageTrans,ffa");
  //LoadModule (env, "BASIC,ffa");
  //LoadModule (env, "BASICTrans,ffa");
  //LoadModule (env, "SharedCLibrary,ffa");
}

// This version is usually called by SWI implementations written in C++
bool RISCOS::SWI (unsigned int swinum)
{
  // Search through module list looking for a module that can handle
  // the SWI request
  for (modules_t_iter x = modlist.begin (); x != modlist.end (); x++)
    {
      if (swinum >= (*x)->SWIChunkBegin () && swinum <= (*x)->SWIChunkEnd ())
	{
	  // Found module, so do it.
	  if ((*x)->SWI (swinum, true))
	    return true;
	}
    }

  return false;
}

// Called by the CPU exception handler.
// Return true if an SWI was successfully handled, or false if not.
bool RISCOS::SWI (CPUARMState *env, unsigned int swinum)
{
  bool xbit;
  xbit = (swinum & 0x20000) ? true : false;
  swinum &= 0xfdffff;

  // Special case for these two SWIs.
  if (swinum == OS_CallASWI)
    {
      swinum = env->regs[10] & 0xfdffff;
      xbit = (env->regs[10] & 0x20000) ? true : false;
    }

 if (swinum == OS_CallASWIR12)
    {
      swinum = env->regs[12] & 0xfdffff;
      xbit = (env->regs[12] & 0x20000) ? true : false;
    }

#if 0
  if (swinum >= 5 && swinum != 0x46 && swinum != 0x23 && swinum != 0x24)
    printf ("SWI %08x r0(%08x) r1(%08x) r2(%08x) pc(%08x)\n",
	    swinum, env->regs[0], env->regs[1], env->regs[2],
	    env->regs[15]);
#endif

  // Search through module list looking for a module that can handle
  // the SWI request
  for (modules_t_iter x = modlist.begin (); x != modlist.end (); x++)
    {
      if (swinum >= (*x)->SWIChunkBegin () && swinum <= (*x)->SWIChunkEnd ())
	{
	  // This needs to be dropped at some point.
	  (*x)->SetState (env);

	  // Found module, so do it.
	  if ((*x)->SWI (swinum, true))
	    return true;
	}
    }

  // Search through the native RISC OS modules for a match
  for (nmodule_t_iter x = native_modlist.begin ();
       x != native_modlist.end (); x++)
    {
      if (swinum >= x->swi_chunk_base && swinum < x->swi_chunk_base + 0x40)
	{
	  //printf ("SWI matched to module\n");
	  // Jump to module code
	  env->regs[11] = swinum - x->swi_chunk_base;
	  ModuleCall (env, (uint32_t *) x->base, 4);
	  //printf ("SWI returned from module\n");
	  return true;
	}
    }

  // SWI not found.
#if 1
  fprintf (stderr, "SWI %08x not implemented at pc=%08x\n",
	   swinum, env->regs[15]);
#endif
  return false;
}

// Called by main() only, load the first application into RISC OS memory
// and setup the command line arguments, ready for execute.
void RISCOS::Exec (CPUARMState *env, int argc, char *argv[],
		   const char *stdin_name, const char *stdout_name,
		   const char *stderr_name)
{
  string cmd;

  // Fill 'cmd' string with command line args that we will pass into
  // the application via DDEUtils.
  int x = 2;
  cmd = "";
  while (argv[x])
    {
      //fprintf (stderr, "argv[%d]='%s'\n", x, argv[x]);
      if (*(argv[x]) == '\0')
	{
	  // Cope with an empty argument i.e. -classpath ''
	  cmd += "''";
	}
      else
	{
	  // Set true if arg contains a space
	  bool space = false;
	  if (strchr (argv[x], ' '))
	    space = true;

	  if (space)
	    cmd += '\"';

	  char *p = argv[x];
	  while (*p)
	    {
	      if (*p == '\'' || *p == '\"')
		cmd += '\\';
	      cmd += *p ++;
	    }

	  if (space)
	    cmd += '\"';
	}
      x ++;

      // Don't append a space to the last argument
      if (x != argc)
	cmd += ' ';
    }

  if (stdin_name[0] != '\0')
    {
      cmd += " <";
      cmd += stdin_name;
    }

  if (stdout_name[0] != '\0')
    {
      cmd += " >";
      cmd += stdout_name;
    }

  if (stderr_name[0] != '\0')
    {
      if (strcmp (stderr_name, "!dup1") == 0)
	cmd += " 2>&1";
      else
	{
	  cmd += " 2>";
	  cmd += stderr_name;
	}
    }

  //printf ("cmd_line='%s'\n", cmd.c_str ());

  // We keep things simple: OS_CLI gets the program name and DDEUtils
  // gets the rest of the command line arguments.

  // Ensure DDEUtils spares enough space for the command args
  env->regs[0] = cmd.size () + 1;
  riscos->SWI (env, DDEUtils_SetCLSize);

  env->regs[0] = (uint32_t) cmd.c_str (); 
  riscos->SWI (env, DDEUtils_SetCL);

  // Pass real program name to OS_CLI
  env->regs[0] = (uint32_t) argv[1];
  riscos->SWI (env, OS_CLI);
}
