#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/times.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>

#include "riscos.h"
#include "module-sharedclibrary.h"

#define SCL_KERN_BASE 0xc0000
#define SCL_LANG_BASE 0xc0100

bool SharedCLibrary::SWI (unsigned int swinum, bool xbit)
{
  switch (swinum)
    {
    case SharedCLibrary_LibInitAPCS_R: LibInitAPCS_R () ; break;
    case SharedCLibrary_LibInitAPCS_32: LibInitAPCS_32 () ; break;
    default:
      return false;
      break;
    }
  return true;
}

struct stub_desc
{
  int chunk_id;
  uint32_t *vector_base;
  uint32_t *vector_limit;
  uint32_t *static_base;
  uint32_t *static_limit;
};

void SharedCLibrary::LibInitAPCS_R (void)
{
  struct stub_desc *stubs = (struct stub_desc *) READ_REG (0);
  uint32_t wkspace_start = READ_REG (1);
  uint32_t wkspace_limit = READ_REG (2);

  printf ("wkspace_start=%08x wkspace_limit=%08x\n",
	  wkspace_start, wkspace_limit);

  while (stubs->chunk_id != -1)
    {
      printf ("SharedCLib: chunk=%d vector_base=%p vector_lim=%p\n",
	      stubs->chunk_id, stubs->vector_base, stubs->vector_limit);
      printf ("SharedCLib: chunk=%d static_base=%p static_lim=%p\n",
	      stubs->chunk_id, stubs->static_base, stubs->static_limit);
      if (stubs->chunk_id == 1)
	{
	  // Setup Kernel data
	  for (int entry = 0; entry < 48; entry ++)
	    stubs->vector_base[entry] = 0xef000000 | SCL_KERN_BASE | entry;
	}
      else if (stubs->chunk_id == 2)
	{
	  // Setup C library data
	  for (int entry = 0; entry < 183; entry ++)
	    stubs->vector_base[entry] = 0xef000000 | SCL_LANG_BASE | entry;
	}
      stubs ++;
    }
  WRITE_REG (0, wkspace_limit);
  WRITE_REG (1, wkspace_limit - 0x8000);
  WRITE_REG (2, wkspace_limit - 0x8000);
  WRITE_REG (6, 5);
}

void SharedCLibrary::LibInitAPCS_32 (void)
{
  LibInitAPCS_R ();
  WRITE_REG (6, 6);
}

struct kernel_init
{
  void *image_base;
  void *ldesc_start;
  void *ldesc_end;
};

struct language_description
{
  int size;
  int codestart, codeend;
  char *name;
  void *initproc;
  void *finaliseproc;
  void *trapproc;
  void *uncaughttrapproc;
  void *eventproc;
  void *unhandledeventproc;
};

bool SCL::SWI (unsigned int swinum, bool xbit)
{
  bool handled = true;

  printf ("SCL: Kern Entry: %d\n", swinum - SCL_KERN_BASE);
  switch (swinum - SCL_KERN_BASE)
    {
    case 0:
      {
	// kernel_init
	struct kernel_init *kinit = (struct kernel_init *) READ_REG (0);
	struct language_description *lang = (struct language_description *) kinit->ldesc_start;
	uint32_t stack_chunk_base = READ_REG (1);
	uint32_t stack_chunk_top = READ_REG (2);

	stack_chunk_top -= 0x400;
	cmd_line = (char *) (stack_chunk_top + 0x100);
	riscos->SWI (OS_GetEnv);
	strcpy (cmd_line, (char *) READ_REG (0));

	WRITE_REG (10, stack_chunk_top - 0x200);
	WRITE_REG (11, 0); // fp = 0
	WRITE_REG (13, stack_chunk_top);

	dynam_code = stack_chunk_top - 0x100;
	*((uint32_t *) dynam_code) = 0xef000000 | SCL_KERN_BASE | 50;
	WRITE_REG (14, dynam_code);
	WRITE_REG (15, (uint32_t) lang->initproc);
	printf ("_kernel_init: executing at %p, code=%p\n",
		lang->initproc, dynam_code);
	return true;
      }
      break;
    case 7: // char *_kernel_command_string (void)
      WRITE_REG (0, (uint32_t) cmd_line);
      break;
    case 20: // int _kernel_oscli (char *s)
      printf ("_kernel_oscli: %08x '%s'\n", READ_REG (0), (char *) READ_REG (0));
      riscos->SWI (OS_CLI);
      break;

    case 50: // SCL hack to jump to the real code
      // On entry R0 = code address
      WRITE_REG (15, READ_REG (0));
      return true;
      break;

    default:
      handled = false;
      break;
    }

 lang:
  printf ("SCL: Lang Entry: %d\n", swinum - SCL_LANG_BASE);
  if (! handled)
    {
      handled = true;
      switch (swinum - SCL_LANG_BASE)
	{
	case 4:
	  printf ("sp = %08x, sl=%08x\n",
		  READ_REG (13), READ_REG (10));
	  break;
	case 5:
	  break;
	case 18: // _main (const char *cmd_line, void *start_addr)
	  {
	    WRITE_REG (0, 0);
	    uint32_t pc = READ_REG (1);
	    printf ("_main: pc=%08x\n", pc);
	    WRITE_REG (15, pc);
	    return true;
	  }
	  break;
	case 20: // void _clib_initialise (void)
	  uint32_t *sp = (uint32_t *) READ_REG (13);
	  printf ("_clib_initialise called: sp=%p sp[0]=%08x\n",
		  sp, sp[0]);
	  sp[0] = dynam_code;
	  printf ("_clib_initialise called: sp=%p sp[0]=%08x\n",
		  sp, sp[0]);
	  break;
	case 71: // atexit
	  printf ("atexit (%08x)\n", READ_REG (0));
	  WRITE_REG (0, 0);
	  break;
	case 73: // getenv
	  {
	    char *arg = (char *) READ_REG (0);
	    char *env = getenv (arg);
	    WRITE_REG (0, (uint32_t) env);
	  }
	  break;
	case 154: // setlocale
	  {
	    int category = READ_REG (0);
	    char *locale = (char *) READ_REG (1);
	    char *ret = setlocale (category, locale);
	    WRITE_REG (0, (uint32_t) ret);
	  }
	  break;
	default:
	  handled = false;
	  break;
	}
    }

  if (handled)
    {
      uint32_t lr = READ_REG (14);
      WRITE_REG (15, lr);
    }

  return handled;
}
