/****************************************************************************
    Copyright (C) 1987-2005 by Jeffery P. Hansen

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
****************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "gsim.h"

#define STRMAX 1024

/*

   root
    |
    v
  +---+MemNode
  |   |
  +---+      +---+MemNode
  | *-+----->|   |
  +---+      +---+
  |   |      |   |
  +---+      +---+      +---+MemNode
  |   |      | *-+----->|   |
  +---+      +---+      +---+
             |   |      |   |
             +---+      +---+
                        |   |
                        +---+      +---+MemPage
                        | *-+----->| D |
                        +---+      +---+
                                   | D |
                                   +---+
                                   | D |
                                   +---+
                                   | D |
                                   +---+
 */

#define MEMORY_TEST 0

static MemPage *new_MemPage(unsigned base,int bpw)
{
  MemPage *P = (MemPage *) malloc(sizeof(MemPage));
  int n = (bpw<< MEM_BYTEBITS);
  int i;

#if 0
  sendMsg("comment create page @ %x",base);
#endif

  P->motype = MEM_PAGE;
  P->baseAddr = base;
  P->data1 = (char*)malloc(n);
  P->datax = (char*)malloc(n);

  for (i = 0;i < n;i++) {
    P->data1[i] = 0;
    P->datax[i] = MEM_BYTEMASK;
  }

  return P;
}

static void delete_MemPage(MemPage *P)
{
  free(P->data1);
  free(P->datax);
  free(P);
}

static MemNode *new_MemNode()
{
  int i;
  MemNode *N = (MemNode *) malloc(sizeof(MemNode));

#if 0
  sendMsg("comment create node");
#endif

  N->motype = MEM_NODE;
  for (i = 0;i < MEM_BYTEMAX;i++)
    N->ptr[i] = 0;

  return N;
}

static void delete_MemNode(MemNode *N)
{
  int i;
  for (i = 0;i < MEM_BYTEMAX;i++) {
    if (N->ptr[i]) {
      MemBase *C =  (MemBase *) N->ptr[i];
      if (C->motype == MEM_NODE)
	delete_MemNode((MemNode*)C);
      else if (C->motype == MEM_PAGE)
	delete_MemPage((MemPage*)C);
    }
  }
  free (N);
}

Memory *new_Memory(int abits,int dbits)
{
  Memory *M = (Memory*) malloc(sizeof(Memory));
  int i,n;

  for (i = 0,n = 1;n < dbits;n <<= 1,i++);
  M->byte_shift = (i >= 3) ? (i-3) : 0;
  M->bpw = n;
  M->nbytes = (n+7)/8;
  M->root = new_MemNode();

  return M;
}

static MemPage *Memory_getPage(Memory *M,unsigned A)
{ 
  int addr[4];
  MemNode *N;
  int i;

  addr[0] = (A >> 24) & MEM_BYTEMASK;
  addr[1] = (A >> 16) & MEM_BYTEMASK;
  addr[2] = (A >> 8) & MEM_BYTEMASK;
  addr[3] = (A     ) & MEM_BYTEMASK;

  N = M->root;
  for (i = 0;i < 2;i++) {
    if (!N->ptr[addr[i]])
      N->ptr[addr[i]] = new_MemNode();
    N = (MemNode*)N->ptr[addr[i]];
  }

  if (!N->ptr[addr[2]])
    N->ptr[addr[2]] = new_MemPage(A&~MEM_BYTEMASK,M->bpw);
  return (MemPage*)N->ptr[addr[2]];
}

void Memory_lookup(Memory *M,unsigned A,unsigned char **D,unsigned char **U)
{
  MemPage *P = Memory_getPage(M,A);
  unsigned w = (A&MEM_BYTEMASK);
  unsigned b = w << M->byte_shift;

  *D = P->data1 + b;
  *U = P->datax + w;
}

void Memory_flush(Memory *M)
{
  delete_MemNode(M->root);
  M->root = new_MemNode();
}

/*
  Stores a line containing an address and values into a memory.

  Example line: "1000/	10 4e 9a 23 8e"
 */
int Memory_putLine(Memory *M,char *line)
{
  char buf[STRMAX];
  unsigned addr,word;
  char *T;
  unsigned char *d,*u;
  int i;

  if (sscanf(line,"%x / %[^\n]",&addr,buf) != 2) {
    if (sscanf(line,"%s",buf) == 1 && *buf != '#')
      return 1;
    return 0;
  }

  Memory_lookup(M,addr,&d,&u);
  for (T = strtok(buf," \t");T;T = strtok(0," \t")) {
    if (sscanf(T,"%x",&word) != 1)
      return 1;

    for (i = 0;i < M->nbytes;i++)
      d[i] = 0xff & (word >> (i<<3));

    *u++ = 0;
    d += M->nbytes;
    addr++;
    if (!(addr & MEM_BYTEMASK))
      Memory_lookup(M,addr,&d,&u);
  }
  return 0;
}

/*

 */
int Memory_readFile(Memory *M,const char *fileName)
{
  FILE *f;
  char buf[STRMAX];

  if (!(f = openInPath(fileName)))
    return -1;

  while (fgets(buf,STRMAX,f)) {
    int r = Memory_putLine(M,buf);
    if (r != 0) return r;
  }
  return 0;
}

int MemPage_dump(MemPage *P,Memory *M,unsigned base,FILE *f)
{
  int i;
  int need_addr = 1;
  int num_on_line = 0;

  for (i = 0;i < MEM_BYTEMAX;i++) {
    unsigned A = base|i;
    unsigned w = (A&MEM_BYTEMASK);
    unsigned b = w << M->byte_shift;
    unsigned char *D,*U;
    unsigned V;
    int j;

    if (i && !(i & 0x7)) {
      if (num_on_line)
	fprintf(f,"\n");
      num_on_line = 0; 
      need_addr = 1;
    }

    D = P->data1 + b;
    U = P->datax + w;

    V = 0;
    for (j = 0;j < M->nbytes;j++)
      V |= D[j] << (j<<3);

    if (!*U) {
      if (need_addr) {
	fprintf(f,"%x/",A);
	need_addr = 0;
      }
      fprintf(f," %x",V);
      num_on_line++;
    } else
      need_addr = 1;
  }

  if (num_on_line)
    fprintf(f,"\n");

  return 0;
}

void MemNode_dump(MemNode *N,Memory *M,unsigned base,FILE *f)
{
  int i;

  if (N->motype == MEM_PAGE) {
    MemPage_dump((MemPage*)N,M,base,f);
    return;
  }

  for (i = 0;i < MEM_BYTEMAX;i++)
    if (N->ptr[i])
      MemNode_dump((MemNode*)N->ptr[i],M,(base|i)<<8,f);
}

void Memory_dump(Memory *M,FILE *f)
{
  if (M->root)
    MemNode_dump(M->root,M,0x0,f);
}

int Memory_writeFile(Memory *M,const char *fileName)
{
  FILE *f;

  if (!(f = fopen(fileName,"w")))
    return -1;

  Memory_dump(M,f);

  fclose(f);
  return 0;
}

#if MEMORY_TEST
main()
{
  Memory *M = new_Memory(32,32);
  char buf[1024];
  unsigned addr,data;
  unsigned char *d,*u;

  while (printf(">>> "),fgets(buf,1024,stdin)) {
    if (sscanf(buf,"g %x",&addr)) {
      Memory_lookup(M,addr,&d,&u);
      if (*u)
	printf("data = ?\n");
      else
	printf("data = %x\n",*(unsigned*)d);
    } else if (sscanf(buf,"s %x %x",&addr,&data)) {
      Memory_lookup(M,addr,&d,&u);
      *(unsigned*)d = data;
      *(unsigned*)u = 0;
    } else if (sscanf(buf,"l %s",buf)) {
      Memory_readFile(M,buf);
    }
  }
}
#endif
