// Copyright 2009 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package Importer

import Utils "utils"
import Globals "globals"
import Object "object"
import Type "type"
import Universe "universe"


type Importer struct {
	comp *Globals.Compilation;
	debug bool;
	buf string;
	buf_pos int;
	pkg_list [256] *Globals.Package;
	pkg_ref int;
	type_list [1024] *Globals.Type;
	type_ref int;
};


func (I *Importer) ReadObject() *Globals.Object;


func (I *Importer) ReadByte() byte {
	x := I.buf[I.buf_pos];
	I.buf_pos++;
	/*
	if E.debug {
		print " ", x;
	}
	*/
	return x;
}


func (I *Importer) ReadInt() int {
	x := 0;
	s := 0;  // TODO eventually Go will require this to be a uint!
	b := I.ReadByte();
	for b < 128 {
		x |= int(b) << s;
		s += 7;
		b = I.ReadByte();
	}
	// b >= 128
	x |= ((int(b) - 192) << s);
	/*
	if I.debug {
		print " #", x;
	}
	*/
	return x;
}


func (I *Importer) ReadString() string {
	var buf [256] byte;  // TODO this needs to be fixed
	n := I.ReadInt();
	for i := 0; i < n; i++ {
		buf[i] = I.ReadByte();
	}
	s := string(buf)[0 : n];
	if I.debug {
		print ` "`, s, `"`;
	}
	return s;
}


func (I *Importer) ReadPackageTag() int {
	tag := I.ReadInt();
	if I.debug {
		if tag >= 0 {
			print " [P", tag, "]";  // package ref
		} else {
			print "\nP", I.pkg_ref, ":";
		}
	}
	return tag;
}


func (I *Importer) ReadTypeTag() int {
	tag := I.ReadInt();
	if I.debug {
		if tag >= 0 {
			print " [T", tag, "]";  // type ref
		} else {
			print "\nT", I.type_ref, ": ", Type.FormStr(-tag);
		}
	}
	return tag;
}


func (I *Importer) ReadObjectTag() int {
	tag := I.ReadInt();
	if tag < 0 {
		panic "tag < 0";
	}
	if I.debug {
		print "\n", Object.KindStr(tag);
	}
	return tag;
}


func (I *Importer) ReadPackage() *Globals.Package {
	tag := I.ReadPackageTag();
	if tag >= 0 {
		return I.pkg_list[tag];  // package already imported
	}

	ident := I.ReadString();
	file_name := I.ReadString();
	key := I.ReadString();
	
	// Canonicalize package - if it was imported before,
	// use the primary import.
	pkg := I.comp.Lookup(file_name);
	if pkg == nil {
		// new package
		obj := Globals.NewObject(-1, Object.PACKAGE, ident);
		pkg = Globals.NewPackage(file_name, obj, Globals.NewScope(nil));
		I.comp.Insert(pkg);
	} else if key != pkg.key {
		// the package was imported before but the package
		// key has changed
		panic "package key inconsistency";
	}
	I.pkg_list[I.pkg_ref] = pkg;
	I.pkg_ref++;

	return pkg;
}


func (I *Importer) ReadScope() *Globals.Scope {
	if I.debug {
		print " {";
	}

	scope := Globals.NewScope(nil);
	obj := I.ReadObject();
	for obj != nil {
		scope.Insert(obj);
		obj = I.ReadObject();
	}
	
	if I.debug {
		print " }";
	}
	
	return scope;
}


func (I *Importer) ReadType() *Globals.Type {
	tag := I.ReadTypeTag();
	if tag >= 0 {
		return I.type_list[tag];  // type already imported
	}

	typ := Globals.NewType(-tag);
	ptyp := typ;  // primary type

	ident := I.ReadString();
	if len(ident) > 0 {
		// named type
		pkg := I.ReadPackage();
		
		// create corresponding type object
		obj := Globals.NewObject(0, Object.TYPE, ident);
		obj.exported = true;
		obj.typ = typ;
		obj.pnolev = pkg.obj.pnolev;
		typ.obj = obj;

		// canonicalize type
		// (if the type was seen before, use primary instance!)
		ptyp = pkg.scope.InsertImport(obj).typ;
	}
	// insert the primary type into the type table but
	// keep filling in the current type fields
	I.type_list[I.type_ref] = ptyp;
	I.type_ref++;

	switch (typ.form) {
	case Type.ALIAS:
		typ.elt = I.ReadType();

	case Type.ARRAY:
		typ.len_ = I.ReadInt();
		typ.elt = I.ReadType();

	case Type.MAP:
		typ.key = I.ReadType();
		typ.elt = I.ReadType();

	case Type.CHANNEL:
		typ.flags = I.ReadInt();
		typ.elt = I.ReadType();

	case Type.FUNCTION:
		typ.flags = I.ReadInt();
		typ.scope = I.ReadScope();

	case Type.STRUCT, Type.INTERFACE:
		typ.scope = I.ReadScope();

	case Type.POINTER, Type.REFERENCE:
		typ.elt = I.ReadType();

	default:
		panic "UNREACHABLE";
	}

	return ptyp;  // only use primary type
}


func (I *Importer) ReadObject() *Globals.Object {
	tag := I.ReadObjectTag();
	if tag == Object.END {
		return nil;
	}
	
	if tag == Object.TYPE {
		// named types are handled entirely by ReadType()
		typ := I.ReadType();
		if typ.obj.typ != typ {
			panic "inconsistent named type";
		}
		return typ.obj;
	}
	
	ident := I.ReadString();
	obj := Globals.NewObject(0, tag, ident);
	obj.typ = I.ReadType();

	switch (tag) {
	case Object.CONST:
		I.ReadInt();  // should set the value field

	case Object.VAR:
		I.ReadInt();  // should set the address/offset field

	case Object.FUNC:
		I.ReadInt();  // should set the address/offset field
		
	default:
		panic "UNREACHABLE";
	}

	return obj;
}


func (I *Importer) Import(comp* Globals.Compilation, file_name string) *Globals.Package {
	I.comp = comp;
	I.debug = comp.flags.debug;
	I.buf = "";
	I.buf_pos = 0;
	I.pkg_ref = 0;
	I.type_ref = 0;
	
	if I.debug {
		print "importing from ", file_name, "\n";
	}
	
	buf, ok := sys.readfile(file_name);
	if !ok {
		return nil;
	}
	I.buf = buf;
	
	// Predeclared types are "pre-imported".
	for p := Universe.types.first; p != nil; p = p.next {
		if p.typ.ref != I.type_ref {
			panic "incorrect ref for predeclared type";
		}
		I.type_list[I.type_ref] = p.typ;
		I.type_ref++;
	}

	// import package
	pkg := I.ReadPackage();
	{	obj := I.ReadObject();
		for obj != nil {
			obj.pnolev = pkg.obj.pnolev;
			pkg.scope.InsertImport(obj);
			obj = I.ReadObject();
		}
	}
	
	if I.debug {
		print "\n(", I.buf_pos, " bytes)\n";
	}
	
	return pkg;
}


export Import
func Import(comp* Globals.Compilation, pkg_name string) *Globals.Package {
	var I Importer;
	return (&I).Import(comp, Utils.FixExt(pkg_name));
}