#include <stddef.h>
#include <exec/types.h>
#include <exec/execbase.h>
#include <emul/emulinterface.h>
#include <emul/emulregs.h>
#include <proto/exec.h>
#include "elf.h"
#include "a2ixlibrary.h"

#ifdef DEBUG
void dprintf(const char *,...);
#define DB(x) x
#else
#define DB(x)
#endif

void ix_flush_insn_cache(void *, size_t);

#define u  (*(struct user *)(SysBase->ThisTask->tc_TrapData))

struct ixbase {
    struct Library lib;
    int (**basearray)();
};

/* The a4 pointers are stored *before* the start of this struct! */
struct user {
	/* both a magic cookie and a way to get at the library base thru u */
	struct ixbase   *u_ixbase;
	void 		*u_reserved;		/* for future use */

	long		u_a4_pointers_size;	/* number of a4 pointers */
	long		u_a4_pointers[0];
};

extern int (**_ixbasearray)();

void *__get_data(void);
int __dbsize(void);

typedef unsigned long ulong;

/* Table: for each section,

   | ulong num * 2 + 4
   | char *base
   | | ulong hash
   | | ulong offset
   | ulong 0
   | ulong -1
*/
extern const ulong __link_info[];

/* Relocate memory starting at BASE using relocation array F.
   HASHES contains the hash array containing symbolnames of this library.
   OFFSETS is the offset array containing the memory address of each
   object.
   
   If F is NULL, then nothing is done.

   The array OFFSETS ends with -1.
   
   The relocation array F has the following format:
   
   ulong hashes			The number of object references that have to
				be relocated
   | ulong hash			The hash value of a symbolname
   | int types		        The number of types of relocations
   | | int type  	        Type of the relocation
   | | int relocs	        Number of addresses that have to be relocated
   | | | ulong offsets		Offset from BASE
   | | | long addend		Relocate to address of object but add this offset too
*/   

static const ulong *relocate(char *base, const ulong *f, const ulong *syms, ulong dest_base)
{
  int cnt = (f ? *f++ : 0);

  DB(dprintf("relocate(%lx, %lx, %lx, %lx)\ncnt = %d\n", base, f, syms, dest_base, cnt);)

  /* For all object references */
  while (cnt--)
  {
    ulong hash = *f++;
    short types = *f++;
    ulong offset;

    DB(dprintf("hash = %lx, types = %lx\n", hash, types);)

    /* Search for this symbolname */
    while (syms[0] != hash && syms[1] != -1)
      syms += 2;
    offset = dest_base + syms[1];

    DB(dprintf("offset = %lx\n", offset);)

    /* Not found, return an error */
    if (syms[1] == -1)
      return NULL;

    while (types--)
    {
      ulong type = *f++;
      ulong relocs = *f++;

      DB(dprintf("type = %ld, relocs = %ld\n", type, relocs);)

    /* Return success if the address is already correct. Apparently this
       memory area has already been relocated. */
    /*if (*((long *)(base + f[0])) == offsets[ind] + f[relocs])
      return 1;*/

      switch (type)
      {
	case R_PPC_ADDR32:
	  /* For all addresses */
	  while (relocs--)
	  {
	    /* Relocate */
	    DB(dprintf("%lx: %08lx -> %08lx\n", f[0], *(long *)(base + f[0]), offset + f[1]);)
	    *((long *)(base + f[0])) = offset + f[1];
	    f += 2;
	  }
	  break;

	case R_PPC_ADDR16_HA:
	  /* For all addresses */
	  while (relocs--)
	  {
	    /* Relocate */
	    ulong dest = offset + f[1];
	    DB(dprintf("%lx: %08lx -> %08lx\n", f[0], *(long *)(base + f[0] - 2), dest);)
	    *((short *)(base + f[0])) = (dest + ((dest & 0x8000) << 1)) >> 16;
	    f += 2;
	  }
	  break;

	case R_PPC_ADDR16_HI:
	  /* For all addresses */
	  while (relocs--)
	  {
	    /* Relocate */
	    DB(dprintf("%lx: %08lx -> %08lx\n", f[0], *(long *)(base + f[0] - 2), offset + f[1]);)
	    *((short *)(base + f[0])) = (offset + f[1]) >> 16;
	    f += 2;
	  }
	  break;

	case R_PPC_ADDR16_LO:
	  /* For all addresses */
	  while (relocs--)
	  {
	    /* Relocate */
	    DB(dprintf("%lx: %08lx -> %08lx\n", f[0], *(long *)(base + f[0] - 2), offset + f[1]);)
	    *((short *)(base + f[0])) = (short)(offset + f[1]);
	    f += 2;
	  }
	  break;

	default:
	  return NULL;
      }
    }
    syms += 2;
  }
  DB(dprintf("done: f = %lx\n", f);)
  return f;
}

int __LibRelocateInstance(void)
{
  char **bases_orig = (char **)REG_D0;
  char **bases = bases_orig;
  const ulong *relocs = (ulong *)REG_D1;
  char *old_src_data = (char *)REG_D2;
  char *new_src_data = (char *)REG_D3;
  int cnt;
  int ind = 0;
  ulong *hashes, *offsets;
  ulong old_dest_data_start = (ulong)__get_data();
  ulong old_dest_data_end = old_dest_data_start + __dbsize();
  ulong new_dest_data_start = u.u_a4_pointers[-LIBRARY_ID - 4] - 0x8000;

  DB(dprintf("relocateInstance(%lx, %lx, %lx, %lx)\n", bases, relocs, old_src_data, new_src_data);)

  while(*bases)
  {
    const ulong *info = __link_info;
    ulong num;

    DB(dprintf("base = %lx, sz = %d\n", *bases, bases[1]);)
    while (num = *info)
    {
      DB(dprintf("num = %d\n", num);)
      relocs = relocate(*bases == old_src_data ? new_src_data : *bases,
			relocs, info + 2,
			info[1] + (info[1] >= old_dest_data_start &&
				   info[1] <= old_dest_data_end ?
				   new_dest_data_start - old_dest_data_start : 0));
      if (!relocs)
	return 0;
      info += num;
    }
    bases += 2;
  }

  /* Replace the hashvalues of symbolnames in the data hunk with
     the addresses of those objects. Used to implement data-to-data
     references between shared libraries. */
#if 0
  hashes = (ulong *)__link_data_hashes;
  offsets = __link_data_offsets;
  cnt = (libdata ? *libdata++ : 0);

  while (cnt--)
  {
    ulong hash = *libdata;
    
    while (offsets[ind] != -1 && hashes[ind] != hash)
      ind++;
    if (offsets[ind] == -1)
      return 0;
    *libdata++ = offsets[ind++];
  }
#endif

  DB(dprintf("clearing caches\n");)
  _ixbasearray = u.u_ixbase->basearray;
  for (bases = bases_orig; *bases; bases += 2)
    if (*bases != old_src_data)
      ix_flush_insn_cache(bases[0], (size_t)bases[1]);

  return 1;
}
