#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include "elfheaders.h"
#include "memc.h"
extern "C" {
#include "qemu.h"
#include "cpu-all.h"
}

extern MEMC *memc;
extern uint32_t wimpslot;
/**
 * Load ELF executable into memory and execute it
 *
 * \param filename Name of file to load
 * \param pw       Module's private word pointer
 * \param entry    Pointer to location to store entry point
 * \return NULL on success and entry filled in, non-NULL on error
 */
uint32_t loadelf (const char *filename)
{
  Elf32_Ehdr *elf_header = NULL;
  char *prog_headers = NULL;
  char *prog_headers_end = NULL;
  Elf32_Phdr *prog_header = NULL;

  /* Open file */
  int fd = open (filename, O_RDONLY);
  if (fd == -1)
    {
      printf ("loadelf: failed to open %s\n", strerror (errno));
      throw (1);
    }

  /* Allocate memory for ELF image header */
  elf_header = (Elf32_Ehdr *) calloc (1, sizeof(Elf32_Ehdr));
  if (elf_header == NULL)
    {
      printf ("loadelf: no memory for headers\n");
      throw (1);
    }

  /* Read in ELF header from file */
  read (fd, elf_header, sizeof (Elf32_Ehdr));

  /* Check that the image is somewhere near sane */
  if (elf_header->e_ident[EI_MAG0] != ELFMAG0	||
      elf_header->e_ident[EI_MAG1] != ELFMAG1	||
      elf_header->e_ident[EI_MAG2] != ELFMAG2	||
      elf_header->e_ident[EI_MAG3] != ELFMAG3	||
      elf_header->e_ident[EI_CLASS] != ELFCLASS32	|| /* 32 bit */
      elf_header->e_ident[EI_DATA] != ELFDATA2LSB	|| /* LE */
      elf_header->e_ident[EI_VERSION] != EV_CURRENT) {
    free(elf_header);
    close (fd);
    exit (1);
  }

  /* Check there are some program headers in this image */
  if (elf_header->e_phoff == 0)
    {
      goto error;
    }

  //  printf ("prog headers require: %d bytes\n",
  //	  elf_header->e_phentsize * elf_header->e_phnum);

  /* Allocate block for program headers */
  prog_headers = (char *) calloc (elf_header->e_phentsize
				  * elf_header->e_phnum,
				  sizeof(char));
  if (prog_headers == NULL)
    {
      goto error;
    }

  /* point to end of program headers */
  prog_headers_end = (prog_headers +
		      (elf_header->e_phentsize * elf_header->e_phnum));

  //printf("prog_headers: %x ; prog_headers_end: %x\n",
  // prog_headers, prog_headers_end);

  /* read program headers in from file */
  lseek (fd, elf_header->e_phoff, SEEK_SET);
  read (fd, prog_headers, elf_header->e_phentsize * elf_header->e_phnum);

  //printf ("require %d bytes wimpslot\n", slotsize);

  /* load program into wimpslot */
  for (prog_header = (Elf32_Phdr *)prog_headers;
       prog_header <= (Elf32_Phdr *)prog_headers_end;
       prog_header = (Elf32_Phdr *)(((char*)prog_header) +
				    elf_header->e_phentsize))
    {
      switch (prog_header->p_type)
	{
	case PT_LOAD:
	  {
#if 0
	    /* Unprotect all pages that we are about to load data
	       into.  */
	    page_unprotect_range ((uint8_t *) prog_header->p_vaddr,
				  prog_header->p_filesz);
#endif

	    /* load this segment into wimpslot */
	    lseek (fd, prog_header->p_offset, SEEK_SET);
	    read (fd, (void *) ((uint32_t) prog_header->p_vaddr),
		  prog_header->p_filesz);
	    
	    /* zero-init, if necessary */
	    if (prog_header->p_filesz < prog_header->p_memsz)
	      {
		void *start = (void *) (prog_header->p_filesz
					+ (uint32_t) prog_header->p_vaddr);
		uint32_t length = prog_header->p_memsz - prog_header->p_filesz;
		page_unprotect_range ((uint8_t *) start, length);
		memset (start, 0, length);
	      }
	  }
	  break;
	case PT_DYNAMIC:
	  /* This area needs dynamically linking */
	  printf ("dynamic linking required\n");
	  goto error;
	  break;
	default:
	  break;
	}
    }
		       
 error:
  if (elf_header)
    free(elf_header);
  if (prog_headers)
    free(prog_headers);
  close (fd);

  /* Return entry point */
  //printf ("entry point: %x\n", elf_header->e_entry);
  return elf_header->e_entry;
}
