// VisualBoyAdvance - Nintendo Gameboy/GameboyAdvance (TM) emulator.
// Copyright (C) 1999-2003 Forgotten
// Copyright (C) 2004 Forgotten and the VBA development team

// 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include "../gba/GBA.h"
#include "../gba/elf.h"
#include "../common/Port.h"
#include "exprNode.h"

#ifndef __GNUC__
#define strdup _strdup
#endif

extern char *yytext;

#define debuggerReadMemory(addr) \
  READ32LE((&map[(addr)>>24].address[(addr) & map[(addr)>>24].mask]))

const void *exprNodeCleanUpList[100];
int exprNodeCleanUpCount = 0;
Type exprNodeType = { 0, TYPE_base, "int", DW_ATE_signed, 4, 0, {0}, 0 };

void exprNodeClean(const void *m)
{
  exprNodeCleanUpList[exprNodeCleanUpCount++] = m;
}

void exprNodeCleanUp()
{
  for(int i = 0; i < exprNodeCleanUpCount; i++) {
    free((void *)exprNodeCleanUpList[i]);
  }
  exprNodeCleanUpCount = 0;
}

Node *exprNodeIdentifier()
{
  Node *n = (Node *)calloc(1, sizeof(Node));
  n->name = strdup(yytext);

  exprNodeClean(n->name);
  exprNodeClean(n);

  n->print = exprNodeIdentifierPrint;
  n->resolve = exprNodeIdentifierResolve;
  return n;
}

bool exprNodeIdentifierResolve(Node *n, Function *f, CompileUnit *u)
{
  Object *o;
  if(elfGetObject(n->name, f, u, &o)) {
    n->type = o->type;
    n->location = elfDecodeLocation(f, o->location, &n->locType);
    return true;
  } else {
    printf("Object %s not found\n", n->name);
  }
  return false;
}

void exprNodeIdentifierPrint(Node *n)
{
  printf("%s", n->name);
}

Node *exprNodeNumber()
{
  Node *n = (Node *)calloc(1, sizeof(Node));

  exprNodeClean(n);
  n->location = atoi(yytext);
  n->type = &exprNodeType;
  n->locType = LOCATION_value;
  n->print = exprNodeNumberPrint;
  n->resolve = exprNodeNumberResolve;
  return n;
}

bool exprNodeNumberResolve(Node *n, Function *f, CompileUnit *u)
{
  return true;
}

void exprNodeNumberPrint(Node *n)
{
  printf("%d", n->location);
}

Node *exprNodeStar(Node *exp)
{
  Node *n = (Node *)calloc(1, sizeof(Node));
  exprNodeClean(n);

  n->expression = exp;

  n->print = exprNodeStarPrint;
  n->resolve = exprNodeStarResolve;
  return n;
}

bool exprNodeStarResolve(Node *n, Function *f, CompileUnit *u)
{
  if(n->expression->resolve(n->expression, f, u)) {
    if(n->expression->type->type == TYPE_pointer) {
      n->location = n->expression->location;
      if(n->expression->locType == LOCATION_memory) {
        n->location = debuggerReadMemory(n->location);
      } else if(n->expression->locType == LOCATION_register) {
        n->location = reg[n->expression->location].I;
      } else {
        n->location = n->expression->location;
      }
      n->type = n->expression->type->pointer;
      n->locType = LOCATION_memory;
      return true;
    } else {
      printf("Object is not of pointer type\n");
    }
  }
  return false;
}

void exprNodeStarPrint(Node *n)
{
  printf("*");
  n->expression->print(n->expression);
}

Node *exprNodeDot(Node *exp, Node *ident)
{
  Node *n = (Node *)calloc(1, sizeof(Node));
  exprNodeClean(n);

  n->expression = exp;
  n->name = ident->name;

  n->print = exprNodeDotPrint;
  n->resolve = exprNodeDotResolve;
  return n;
}

bool exprNodeDotResolve(Node *n, Function *f, CompileUnit *u)
{
  if(n->expression->resolve(n->expression, f, u)) {
    TypeEnum tt = n->expression->type->type;

    if(tt == TYPE_struct ||
       tt == TYPE_union) {
      u32 loc = n->expression->location;
      Type *t = n->expression->type;
      int count = t->structure->memberCount;
      int i = 0;
      while(i < count) {
        Member *m = &t->structure->members[i];
        if(strcmp(m->name, n->name) == 0) {
          // found member
          n->type = m->type;
          if(tt == TYPE_struct) {
            n->location = elfDecodeLocation(f, m->location, &n->locType,
                                            loc);
            n->objLocation = loc;
          } else {
            n->location = loc;
            n->locType = n->expression->locType;
            n->objLocation = loc;
          }
          n->member = m;
          return true;
        }
        i++;
      }
      printf("Member %s not found\n", n->name);
    } else {
      printf("Object is not of structure type\n");
    }
  }
  return false;
}

void exprNodeDotPrint(Node *n)
{
  n->expression->print(n->expression);
  printf(".%s", n->name);
}

Node *exprNodeArrow(Node *exp, Node *ident)
{
  Node *n = (Node *)calloc(1, sizeof(Node));
  exprNodeClean(n);

  n->expression = exp;
  n->name = ident->name;

  n->print = exprNodeArrowPrint;
  n->resolve = exprNodeArrowResolve;
  return n;
}

bool exprNodeArrowResolve(Node *n, Function *f, CompileUnit *u)
{
  if(n->expression->resolve(n->expression, f, u)) {
    TypeEnum tt = n->expression->type->type;
    if(tt != TYPE_pointer) {
      printf("Object not of pointer type\n");
      return false;
    }
    tt = n->expression->type->pointer->type;

    if(tt == TYPE_struct ||
       tt == TYPE_union) {
      u32 loc = debuggerReadMemory(n->expression->location);
      Type *t = n->expression->type->pointer;
      int count = t->structure->memberCount;
      int i = 0;
      while(i < count) {
        Member *m = &t->structure->members[i];
        if(strcmp(m->name, n->name) == 0) {
          // found member
          n->type = m->type;
          if(tt == TYPE_struct) {
            n->location = elfDecodeLocation(f, m->location, &n->locType,
                                            loc);
            n->objLocation = loc;
          } else {
            n->location = loc;
            n->objLocation = loc;
          }
          n->locType = LOCATION_memory;
          n->member = m;
          return true;
        }
        i++;
      }
      printf("Member %s not found\n", n->name);
    } else {
      printf("Object is not of structure type\n");
    }
  }
  return false;
}

void exprNodeArrowPrint(Node *n)
{
  n->expression->print(n->expression);
  printf("->%s", n->name);
}

Node *exprNodeAddr(Node *exp)
{
  Node *n = (Node *)calloc(1, sizeof(Node));
  exprNodeClean(n);

  n->expression = exp;

  n->print = exprNodeAddrPrint;
  n->resolve = exprNodeAddrResolve;
  return n;
}

bool exprNodeAddrResolve(Node *n, Function *f, CompileUnit *u)
{
  if(n->expression->resolve(n->expression, f, u)) {
    if(n->expression->locType == LOCATION_memory) {
      n->location = n->expression->location;
      n->locType = LOCATION_value;
      n->type = &exprNodeType;
    } else if(n->expression->locType == LOCATION_register) {
      printf("Value is in register %d\n", n->expression->location);
    } else {
      printf("Direct value is %d\n", n->location);
    }
    return true;
  }
  return false;
}

void exprNodeAddrPrint(Node *n)
{
  printf("*");
  n->expression->print(n->expression);
}

Node *exprNodeSizeof(Node *exp)
{
  Node *n = (Node *)calloc(1, sizeof(Node));
  exprNodeClean(n);

  n->expression = exp;

  n->print = exprNodeSizeofPrint;
  n->resolve = exprNodeSizeofResolve;
  return n;
}

bool exprNodeSizeofResolve(Node *n, Function *f, CompileUnit *u)
{
  if(n->expression->resolve(n->expression, f, u)) {
    n->location = n->expression->type->size;
    n->locType = LOCATION_value;
    n->type = &exprNodeType;
    return true;
  }
  return false;
}

void exprNodeSizeofPrint(Node *n)
{
  printf("sizeof(");
  n->expression->print(n->expression);
  printf(")");
}

Node *exprNodeArray(Node *exp, Node *number)
{
  Node *n = (Node *)calloc(1, sizeof(Node));
  exprNodeClean(n);

  n->expression = exp;
  n->value = number->location;

  n->print = exprNodeArrayPrint;
  n->resolve = exprNodeArrayResolve;
  return n;
}

int exprNodeGetSize(Array *a, int index)
{
  index++;
  if(index == a->maxBounds) {
    return a->type->size;
  } else {
    int size = a->bounds[a->maxBounds-1] * a->type->size;

    for(int i = index; i < a->maxBounds-1; i++) {
      size *= a->bounds[i];
    }
    return size;
  }
}

bool exprNodeArrayResolve(Node *n, Function *f, CompileUnit *u)
{
  if(n->expression->resolve(n->expression, f, u)) {
    TypeEnum tt = n->expression->type->type;
    if(tt != TYPE_array &&
       tt != TYPE_pointer) {
      printf("Object not of array or pointer type\n");
      return false;
    }

    if(tt == TYPE_array) {
      Array *a = n->expression->type->array;

      u32 loc = n->expression->location;
      Type *t = a->type;
      if(a->maxBounds > 1) {
        int index = n->expression->index;

        if(index == a->maxBounds) {
          printf("Too many indices for array\n");
          return false;
        }

        if((index+1) < a->maxBounds) {
          n->type = n->expression->type;
          n->index = index+1;
          n->locType = LOCATION_memory;
          n->location = n->expression->location +
            n->value * exprNodeGetSize(a, index);
          return true;
        }
      }
      n->type = t;
      n->location = loc + n->value * t->size;
      n->locType = LOCATION_memory;
    } else {
      Type *t = n->expression->type->pointer;
      u32 loc = n->expression->location;
      if(n->expression->locType == LOCATION_register)
        loc = reg[loc].I;
      else
        loc = debuggerReadMemory(loc);
      n->type = t;
      n->location = loc + n->value * t->size;
      n->locType = LOCATION_memory;
    }
    return true;
  }
  return false;
}

void exprNodeArrayPrint(Node *n)
{
  n->expression->print(n->expression);
  printf("[%d]", n->value);
}
