/* ScummC
 * Copyright (C) 2004-2006  Alban Bedel
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */

/**
 * @file scc_ns.c
 * @ingroup scc sld
 * @brief ScummC namespace
 */

#include "config.h"

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

#include "scc_util.h"
#include "scc_parse.h"
#include "scc_ns.h"


void scc_symbol_list_free(scc_symbol_t* s);

void scc_symbol_free(scc_symbol_t* s) {
  if(s->sym) free(s->sym);
  if(s->childs) scc_symbol_list_free(s->childs);
  free(s);
}

void scc_symbol_list_free(scc_symbol_t* s) {
  scc_symbol_t* n;

  while(s) {
    n = s->next;
    scc_symbol_free(s);
    s = n;
  }
}

int scc_sym_is_var(int type) {
  if(type == SCC_RES_VAR ||
     type == SCC_RES_BVAR ||
     type == SCC_RES_LVAR)
    return 1;
  return 0;
}

int scc_sym_is_global(int type) {
  if(type == SCC_RES_VAR ||
     type == SCC_RES_BVAR ||
     type == SCC_RES_ROOM ||
     type == SCC_RES_VERB ||
     type == SCC_RES_ACTOR ||
     type == SCC_RES_CLASS)
    return 1;
  return 0;
}

int scc_sym_is_only_global(int type) {
  if(type == SCC_RES_ROOM ||
     type == SCC_RES_VERB ||
     type == SCC_RES_ACTOR ||
     type == SCC_RES_CLASS)
    return 1;
  return 0;
}

scc_ns_t* scc_ns_new(scc_target_t* target) {
  scc_ns_t* ns = calloc(1,sizeof(scc_ns_t));
  ns->target = target;
  return ns;
}

void scc_ns_free(scc_ns_t* ns) {
  scc_symbol_list_free(ns->glob_sym);
  free(ns);
}

scc_symbol_t* scc_ns_get_sym(scc_ns_t* ns, char* room, char* sym) {
  scc_symbol_t* r;
  scc_symbol_t* cur;

  if(room) {
    for(r = ns->glob_sym ; r ; r = r->next) {
      if(r->type != SCC_RES_ROOM) continue;
      if(!strcmp(r->sym,room)) break;
    }
    if(!r) return NULL;
    cur = r->childs;
  } else if(ns->cur) {
    if(ns->cur->childs)
      cur = ns->cur->childs;
    else if(ns->cur->parent)
      cur = ns->cur->parent->childs;
    else
      cur = ns->glob_sym;
  } else {
    cur = ns->glob_sym;
  }
    

  while(cur) {
    for(r = cur ; r ; r = r->next) {
      if(strcmp(r->sym,sym)) continue;
      return r;
    }
    // for full name we don't want to continue the search
    if(room) break;
    if(cur->parent) {
      if(cur->parent->parent)
	cur = cur->parent->parent->childs;
      else
	cur = ns->glob_sym;
    } else
      cur = NULL;
  }

  return NULL;
}

scc_symbol_t* scc_ns_get_sym_with_id(scc_ns_t* ns,int type, int id) {
  scc_symbol_t* r,*r2;

  // room, verbs and variables are in the global ns
  if(scc_sym_is_global(type)) {
    for(r = ns->glob_sym ; r ; r = r->next) {
      if(r->type == type && r->rid == id) return r;
    }
    if(scc_sym_is_only_global(type))
      return NULL;
  }

  // local vars are in the current sym
  if(type == SCC_RES_LVAR) {
    for(r = ns->cur ; r ; r = r->next) {
      if(r->type == SCC_RES_LVAR && r->rid == id) return r;
    }
    return NULL;
  }
      

  // the rest is in some room ... somewhere :)
  for(r = ns->glob_sym ; r ; r = r->next) {
    if(r->type != SCC_RES_ROOM) continue;
    for(r2 = r->childs ; r2 ; r2 = r2->next) {
      if(r2->type == type && r2->rid == id) return r2;
    }
  }

  return NULL;
}

// check if a redeclaration is compatible with the one we had before
static int scc_ns_redecl_sym(scc_ns_t* ns, scc_symbol_t* i, 
			     int type, int subtype,
			     int addr) {
  if(i->type != type || i->subtype != subtype) {
    scc_log(LOG_ERR,"Symbol %s is already defined with another type.\n",
            i->sym);
    return 0;
  }
  if(i->addr >= 0 && addr >= 0 && i->addr != addr) {
    scc_log(LOG_ERR,"Symbol %s is already defined with address %d.\n",
            i->sym,i->addr);
    return 0;
  }

  if(i->addr < 0 && addr)
    return scc_ns_set_sym_addr(ns,i,addr);

  return 1;
}

scc_symbol_t*  scc_ns_add_sym(scc_ns_t* ns, char* sym,
			      int type, int subtype,
			      int addr,char status) {
  scc_symbol_t* rr = scc_ns_get_sym(ns,NULL,sym);

  if(rr) {
    // check for address eventual type/address clash
    if(!scc_ns_redecl_sym(ns,rr,type,subtype,addr)) return 0;
    // the new sym export
    if(status == 'E') {
      // alredy exported ??
      if(rr->status == 'E') {
	scc_log(LOG_ERR,"Symbol %s is exported more than once.\n",sym);
	return 0;
      }
      rr->status = 'E';
    }
    return rr;
  }

  scc_log(LOG_DBG,"Adding %s symbol %s (%d-%d @ %d)\n",
          status == 'E' ? "exported" : "imported",
          sym,type,subtype,addr);

  rr = calloc(1,sizeof(scc_symbol_t));
  rr->type = type;
  rr->subtype = subtype;
  rr->sym = strdup(sym);
  rr->addr = -1;
  rr->status = status;

  if(addr >= 0) {
    if(!scc_ns_set_sym_addr(ns,rr,addr)) {
      scc_symbol_free(rr);
      return NULL;
    }
  }

  if(ns->cur) {
    rr->next = ns->cur->childs;
    ns->cur->childs = rr;
    rr->parent = ns->cur;
  } else {
    rr->next = ns->glob_sym;
    ns->glob_sym = rr;
  }

  return rr;
}

static int scc_ns_make_addr(int type, int subtype, int addr, int loff) {

  if(addr < 0) return addr;

  if(type == SCC_RES_BVAR) addr |= 0x8000;
  else if(type == SCC_RES_LVAR) addr |= 0x4000;
  else if(type == SCC_RES_LSCR) addr += loff;

  return addr;
}

scc_symbol_t* scc_ns_decl(scc_ns_t* ns, char* room, char* sym, 
			  int type, int subtype, int addr) {
  scc_symbol_t* rr;
  scc_symbol_t* new;

  addr = scc_ns_make_addr(type,subtype,addr,ns->target->max_global_scr);

  if(room) {
      // go into a room ns
      rr = scc_ns_get_sym(ns,NULL,room);
      if(!rr || rr->type != SCC_RES_ROOM) {
	scc_log(LOG_ERR,"%s is not a declared room.\n",room);
	return NULL;
      }
      // return scc_ns_decl_in_room(ns,rr,sym,type,subtype,addr);
  } else {
    rr = scc_ns_get_sym(ns,NULL,sym);
    if(rr) {
      if(scc_ns_redecl_sym(ns,rr,type,subtype,addr)) return rr;
      return NULL;
    }
  }
  
  new = calloc(1,sizeof(scc_symbol_t));
  new->type = type;
  new->subtype = subtype;
  new->sym = strdup(sym);

  if(addr >= 0) {
    if(!scc_ns_set_sym_addr(ns,new,addr)) {
      scc_symbol_free(new);
      return NULL;
    }
  } else
      new->addr = addr;

  if(room) {
    new->next = rr->childs;
    rr->childs = new;
    new->parent = rr;
  } else if(ns->cur) {
    new->next = ns->cur->childs;
    ns->cur->childs = new;
    new->parent = ns->cur;
  } else {
    new->next = ns->glob_sym;
    ns->glob_sym = new;
  }
    
  return new;
  
}

int scc_ns_push(scc_ns_t* ns, scc_symbol_t* s) {

  if(s->parent != ns->cur) {
    scc_log(LOG_ERR,"Trying to push sym %s into the ns, but it doesn't belong to the current level %s\n",s->sym,ns->cur->sym);
    return 0;
  }
  
  ns->cur = s;
  return 1;
}

void scc_ns_clear(scc_ns_t* ns,int type) {
  scc_symbol_t *s,*o = NULL;

  if(!ns->cur) {
    scc_log(LOG_ERR,"Trying to clear ns, but there are no pushed syms!!!\n");
    return;
  }

  s = ns->cur->childs;
  while(s) {
    
    if(s->type != type) {
      o = s;
      s = s->next;
      continue;
    }
     
    if(o) {
      o->next = s->next;
      scc_symbol_free(s);
      s = o->next;
    } else {
      ns->cur->childs = s->next;
      scc_symbol_free(s);
      s = ns->cur->childs;
    }
  }
  memset(&ns->as[type],0,0x10000/8);
}

int scc_ns_pop(scc_ns_t* ns) {


  if(!ns->cur) {
    scc_log(LOG_ERR,"Trying to pop ns, but there is no pushed sym !!!\n");
    return 0;
  }

  ns->cur = ns->cur->parent;
  return 1;
}

// allocate a ressource id for this symbol
int scc_ns_get_rid(scc_ns_t* ns, scc_symbol_t* s) {
  scc_log(LOG_DBG,"Get rid for %s\n",s->sym);
  // make sure the parent symbols all have rid allocated
  if(s->parent && !s->parent->rid) scc_ns_get_rid(ns,s->parent);

  if(s->rid > 0) return s->rid;

  // bit variable have there own address space
  ns->rids[s->type]++;
  s->rid = ns->rids[s->type];

  return s->rid;
}

int scc_ns_set_sym_addr(scc_ns_t* ns, scc_symbol_t* s,int addr) {
  uint8_t* as = ns->as[s->type];

  if(addr == s->addr) return 1;

  if(addr > ns->target->addr_max[s->type]) {
    scc_log(LOG_ERR,"Address 0x%X is invalid for its type.\n",addr);
    return 0;
  }

  if(as[addr/8] & (1 << (addr%8))) {
    scc_log(LOG_ERR,"Address 0x%X is already in use.\n",addr);
    return 0;
  }
  as[addr/8] |= (1 << (addr%8));

  s->addr = addr;
  return 1;
}

int scc_ns_alloc_sym_addr(scc_ns_t* ns,scc_symbol_t* s,int* cur) {
  uint8_t* as = ns->as[s->type];
  int i;

  if(s->addr >= 0) return 1;

  for(i = cur[0] ; i <= ns->target->addr_max[s->type] ; i++) {
    if(as[i/8] & (1 << (i%8))) continue;
    as[i/8] |= 1 << (i%8);
    cur[0]++;
    s->addr = i;
    return 1;
  }
  return 0;

}

int scc_ns_alloc_addr(scc_ns_t* ns) {
  scc_symbol_t* r,*s;
  int cur[SCC_RES_LAST];

  memcpy(cur,ns->target->addr_min,SCC_RES_LAST*sizeof(int));

  scc_log(LOG_DBG,"Allocating address:\n");
  for(r = ns->glob_sym ; r ; r = r->next) {
    if(!scc_ns_alloc_sym_addr(ns,r,&cur[r->type])) {
      scc_log(LOG_ERR,"Failed to allocate an address for %s.\n",r->sym);
      return 0;
    }
    scc_log(LOG_DBG,"%s @ 0x%X\n",r->sym,r->addr);
    if(r->type == SCC_RES_ROOM) {
      for(s = r->childs ; s ; s = s->next) {
	if(!scc_ns_alloc_sym_addr(ns,s,&cur[s->type])) {
	  scc_log(LOG_ERR,"Failed to allocate an address for %s::%s.\n",
                  r->sym,s->sym);
	  return 0;
	}
	scc_log(LOG_DBG,"%s::%s @ 0x%X\n",r->sym,s->sym,s->addr);
      }
    }
  }

  return 1;
}

static int scc_symbol_get_addr_from(scc_symbol_t* s,scc_symbol_t* ref) {
  if(ref->addr < 0) {
    scc_log(LOG_ERR,"The symbol %s has no address in the src ns.\n",s->sym);
    return 0;
  }

  if(s->addr >= 0 && s->addr != ref->addr) {
    scc_log(LOG_ERR,"Got address clash on symbol %s: 0x%X != 0x%X ????\n",s->sym,
            s->addr,ref->addr);
  }

  if(ref->type != s->type ||
     ref->subtype != s->subtype) {
    scc_log(LOG_ERR,"The symbol %s is not of the same type in src ns.\n",s->sym);
    return 0;
  }
  s->addr = ref->addr;
  return 1;
}


int scc_ns_get_addr_from(scc_ns_t* ns,scc_ns_t* from) {
  scc_symbol_t* r,*s,*ref;

  for(r = ns->glob_sym ; r ; r = r->next) {
    ref = scc_ns_get_sym(from,NULL,r->sym);
    if(!ref) {
      scc_log(LOG_ERR,"Failed to find symbol %s in the scr ns.\n",r->sym);
      return 0;
    }
    if(!scc_symbol_get_addr_from(r,ref)) return 0;
    if(r->type == SCC_RES_ROOM) {
      for(s = r->childs ; s ; s = s->next) {
	ref = scc_ns_get_sym(from,r->sym,s->sym);
	if(!ref) {
          scc_log(LOG_ERR,"Failed to find symbol %s::%s in the scr ns.\n",
                  r->sym,s->sym);
	  return 0;
	}
	if(!scc_symbol_get_addr_from(s,ref)) return 0;
      }
    }
  }
  return 1;
}


int scc_ns_res_max(scc_ns_t* ns,int type) {
  scc_symbol_t* r,*s;
  int n = 0;

  if(scc_sym_is_global(type)) {
    for(r = ns->glob_sym ; r ; r = r->next) {
      if(r->type == type && r->addr > n) n = r->addr;
    }
    if(type == SCC_RES_BVAR)
      n &= 0x7FFF;

    if(scc_sym_is_only_global(type))
      return n;
  }
   
  for(r = ns->glob_sym ; r ; r = r->next) {
    if(r->type != SCC_RES_ROOM) continue;
    for(s = r->childs ; s ; s = s->next) {
      if(s->type == type && s->addr > n) n = s->addr;
    }
  }
  return n;

}

int scc_ns_is_addr_alloc(scc_ns_t* ns,int type,int addr) {
  if(addr < 0 || type < 0 || type >= SCC_RES_LAST) return 0;

  return (ns->as[type][addr/8] & (1<<(addr%8))) ? 1 : 0;
}

scc_symbol_t* scc_ns_get_sym_at(scc_ns_t* ns,int type,int addr) {
  scc_symbol_t* r,*r2;

  // room, verbs and variables are in the global ns
  if(scc_sym_is_global(type)) {
    for(r = ns->glob_sym ; r ; r = r->next) {
      if(r->type == type && r->addr == addr) return r;
    }
    if(scc_sym_is_only_global(type))
      return NULL;
  }

  // local vars are in the current sym
  if(type == SCC_RES_LVAR) {
    for(r = ns->cur ; r ; r = r->next) {
      if(r->type == SCC_RES_LVAR && r->addr == addr) return r;
    }
    return NULL;
  }
      

  // the rest is in some room ... somewhere :)
  for(r = ns->glob_sym ; r ; r = r->next) {
    if(r->type != SCC_RES_ROOM) continue;
    for(r2 = r->childs ; r2 ; r2 = r2->next) {
      if(r2->type == type && r2->addr == addr) return r2;
    }
  }
  
  return NULL;
}
