package s3.core.services.memory;

import ovm.core.OVMBase;
import ovm.core.domain.Blueprint;
import ovm.core.domain.DomainDirectory;
import ovm.core.domain.ObjectModel;
import ovm.core.execution.Activation;
import ovm.core.execution.CoreServicesAccess;
import ovm.core.execution.Context;
import ovm.core.execution.Native;
import ovm.core.services.memory.MovingGC;
import ovm.core.services.memory.NonPinningMemoryManager;
import ovm.core.services.memory.VM_Address;
import ovm.core.services.memory.VM_Word;
import ovm.util.CommandLine;
import ovm.util.NumberRanges;
import s3.core.domain.S3Blueprint;
import s3.util.PragmaNoInline;
import ovm.core.services.memory.VM_Area;
import s3.util.PragmaNoPollcheck;
import s3.util.PragmaAtomic;
import ovm.core.domain.Oop;

public class S3HalfSpaceAllocator extends NonPinningMemoryManager {
  
    /**
     * Amount of memory this allocator can handle. In bytes.
     **/
    private final int MAX_MEM_SIZE;
    private final int HALF_SPACE_SIZE;
    private final int GC_THRESHOLD;
  
    private static OutOfMemoryError oome;
    private VM_Address memory_;
    private int pos_;
   // private VM_Address gctop_;
   private VM_Address secondhalf;
    private int half_;
    private boolean collecting;
    private VM_Address free_;
    private S3VMInterface vmint;
    private VM_Address beginFromSpace;
  
    private int allocated = 0;
    private boolean inGC;
  
    private static Object allocatorLock = new Object();
  
    private static final boolean CHECK_LOCK_ORDER = true;

    // this is executed first in CSA.boot()
    public final void boot() {
        this.memory_ = Native.getmem(MAX_MEM_SIZE);
        S3HalfSpaceAllocator.oome = new OutOfMemoryError();
        this.secondhalf = this.memory_.add(HALF_SPACE_SIZE);        
        this.vmint = new S3VMInterface(this);
        S3VMInterface.instance = vmint;
	heapArea.setupDestructors(MAX_MEM_SIZE);
    }

    public void fullyBootedVM() {
	super.fullyBootedVM();
	
        if (CHECK_LOCK_ORDER) {
	    CoreServicesAccess csa =
		DomainDirectory.getExecutiveDomain().getCoreServicesAccess();
            // pre-fatten/allocate allocator lock to recursive calls
            // to allocate()
	    csa.ensureMonitor(VM_Address.fromObject(allocatorLock).asOop());
        }
    }
  
  //  public void init(){}

  public S3HalfSpaceAllocator( String heapSize) {
    MAX_MEM_SIZE = CommandLine.parseSize( heapSize);
    HALF_SPACE_SIZE = MAX_MEM_SIZE >> 1;
    GC_THRESHOLD = MAX_MEM_SIZE / 3;
    
    this.memory_ = null;
    this.pos_ = 0;
    this.half_ = 0; 
    collecting = false;
  }

    public boolean isPrecise() { return true; }

    public VM_Address getMem(int size) throws PragmaAtomic {
        collecting = true;
    
        allocated += size;
        int current = pos_;
    
        if (current + size <= GC_THRESHOLD) {
            pos_ += size;
            collecting = false;
            VM_Address ptr = memory_.add((half_ * HALF_SPACE_SIZE)+current);
            VM_Address end = ptr.add(size);
            VM_Word word = VM_Word.fromInt(0);
            while(ptr.uLT(end)){
                ptr.setWord(word);
                ptr = ptr.add(VM_Word.widthInBytes());
            }
            return memory_.add((half_ * HALF_SPACE_SIZE)+current);
        } else {
            // make sure nothing is pinned before doing GC
            synchronized(gcCriticalRegionLock) {
                doGC();
            }
            current = pos_;
            collecting = false;
            if (current + size <= GC_THRESHOLD) {
                pos_ += size;
                VM_Address ptr = memory_.add((half_ * HALF_SPACE_SIZE)+current);
                VM_Address end = ptr.add(size);
                VM_Word word = VM_Word.fromInt(0);
                while(ptr.uLT(end)){
                    ptr.setWord(word);
                    ptr = ptr.add(VM_Word.widthInBytes());
                }
      
                return memory_.add((half_ * HALF_SPACE_SIZE)+current);
            }
            throw oome;
        }
    }

    private SpaceScanner scanner = null;
    
    protected void doGC() throws PragmaNoInline {
        Native.print_string("GC is running\n");
        allocated = 0;
        beginFromSpace = memory_.add(half_*HALF_SPACE_SIZE);
        half_ = (half_ == 0)?1:0;
        free_ = memory_.add(half_*HALF_SPACE_SIZE);
        pos_  = 0;
      
        S3AddressQueue roots = new S3AddressQueue();
        S3AddressQueue objects = new S3AddressQueue();
        Activation act = Context.getCurrentContext().getCurrentActivation();
        act = act.caller(act);
        S3VMInterface.findAllRoots(roots, objects, act);
      
      
        VM_Address fldaddress, fldcontent, newaddr;
        while(roots.isNonEmpty()){
            fldaddress = roots.pop();
            fldcontent = fldaddress.getAddress();
            if(inFromSpace(fldcontent)){
                newaddr = (vmint.hasObjectMoved(fldcontent.asOop()))?
                    ((MovingGC)fldcontent.asAnyOop()).getForwardAddress():
                    vmint.moveObject(fldcontent.asOop(), (Blueprint)null);
                fldaddress.setAddress(newaddr);
            }
        }
      
        VM_Address currobj;
      
        while(objects.isNonEmpty()){
            currobj = objects.pop();
            MovingGC o = (MovingGC)currobj.asAnyOop();
            for ( int k = ObjectModel.getObjectModel().maxReferences(); k --> 0; ) {
                MovingGC ref = (MovingGC)o.getReference( k).asAnyOop();
                if ( null == ref ) continue;
                if ( ! ref.isForwarded() ){
                    vmint.moveObject(ref, (Blueprint)null);
                }
                o.updateReference(k,ref.getForwardAddress().asOop());
            }
        }
      
        scanner = new SpaceScanner(memory_.add(half_ * HALF_SPACE_SIZE),
				   memory_.add((half_ * HALF_SPACE_SIZE) + pos_));
	VM_Area.DestructorWalker updater = heapArea.new DestructorUpdater();
	for (int kind = 0; kind <= VM_Area.Destructor.N_KINDS; kind++) {
	    S3Blueprint currbp;
	    //Field currfield;
	    while(scanner.hasNext()){
		currobj = scanner.next();
		MovingGC o = (MovingGC)currobj.asAnyOop();
		for ( int k = ObjectModel.getObjectModel().maxReferences(); k --> 0; ) {
		    MovingGC ref = (MovingGC)o.getReference( k).asAnyOop();
		    if ( null == ref ) continue;
		    if (inFromSpace(VM_Address.fromObject(ref))){
			if( !ref.isForwarded() ){
			    vmint.moveObject(ref, (Blueprint)null);
			    scanner.updateExtent(memory_.add((half_*HALF_SPACE_SIZE)+pos_));
			}
			o.updateReference(k,ref.getForwardAddress().asOop());
		    }
		}
		currbp = (S3Blueprint)o.getBlueprint();
		if(currbp.isArray()){
		    if(((S3Blueprint)currbp.asArray().getComponentBlueprint()).isReference()){
			for(int i=0; i<currbp.asArray().getLength(o); i++){
			    VM_Address component = (currobj.add(((S3Blueprint.Array)currbp.asArray()).byteOffset(i))).getAddress();
			    if(component.isNonNull()){
				if(inFromSpace(component)){
				    if(!(((MovingGC)component.asAnyOop()).isForwarded())){
					vmint.moveObject(component.asOop(), currbp.asArray().getComponentBlueprint());
					scanner.updateExtent(memory_.add((half_*HALF_SPACE_SIZE)+pos_));
				    }
				    (currobj.add(((S3Blueprint.Array)currbp.asArray()).byteOffset(i))).setAddress(((MovingGC)component.asAnyOop()).getForwardAddress());
				}
			    }
                      
			}
		    }
		}
		else{
		    int[] refmap = currbp.getRefMap();
		    for(int i=0; i<refmap.length; i++){
			fldaddress = currobj.add(refmap[i]);
			fldcontent = fldaddress.getAddress();
			if(fldcontent.isNonNull() && inFromSpace(fldcontent)){
			    if(!(((MovingGC)fldcontent.asAnyOop()).isForwarded())){
				vmint.moveObject(fldcontent.asOop(), currbp);
				scanner.updateExtent(memory_.add((half_*HALF_SPACE_SIZE)+pos_));
			    }
			    fldaddress.setAddress(((MovingGC)fldcontent.asAnyOop()).getForwardAddress());
			}
		    }
		}
	    }
	    if (kind != VM_Area.Destructor.N_KINDS)
		heapArea.walkDestructors(kind, updater);
	}
	scanner = null;
    }
  
    public boolean inFromSpace(VM_Address test){
        if(test.isNull()) return false;
        VM_Word off = test.diff(beginFromSpace);
        return off.uLT(VM_Word.fromInt(HALF_SPACE_SIZE));
    }
  
    public void checkRegionIntegrity(SpaceScanner scanner){
        S3Blueprint currbp;
        //Field.Iterator fields;
        //Field currfield;
        VM_Address currobj;
        int count = 0;
        VM_Address fldaddress;
        VM_Address fldcontent;
    
        while(scanner.hasNext()){
            currobj = scanner.next();
            count++;
            MovingGC o = (MovingGC)currobj.asAnyOop();
            for ( int k = ObjectModel.getObjectModel().maxReferences(); k --> 0; ) {
                MovingGC ref = (MovingGC)o.getReference( k).asAnyOop();
                if ( null == ref ) continue;
                if (inFromSpace(VM_Address.fromObject(o.getReference( k)))){
                    Native.print_string("Found pointer to from space in object model.");
                    Native.print_string("Object blueprint: " + o + "\n");
                }
            }
            currbp = (S3Blueprint)o.getBlueprint();
            if(currbp.isArray()){
                if(((S3Blueprint)currbp.asArray().getComponentBlueprint()).isReference()){
                    for(int i=0; i<currbp.asArray().getLength(currobj.asOop()); i++){
                        VM_Address component = (currobj.add(((S3Blueprint.Array)currbp.asArray()).byteOffset(i))).getAddress();
                        if(component.isNonNull()){
                            if(inFromSpace(component)){
                                Native.print_string("Found pointer to from space inside an array\n");
                            }
                        }
                    }
                }
            }
            else{
                int[] refmap = currbp.getRefMap();
                for(int i=0; i<refmap.length; i++){
                    fldaddress = currobj.add(refmap[i]);
                    fldcontent = fldaddress.getAddress();
                    if(!(fldcontent.isNull()) && inFromSpace(fldcontent)){
                        Native.print_string("Found pointer to from space in field. ");
                        Native.print_string("fldaddr: " + fldaddress.asInt() + " fldcontent: " + fldcontent.asInt() + "\n");
                    }
                }
            }
        }
        Native.print_string("Examined " + count + " objects\n");
    }

    private final VM_Area heapArea = new VM_Area() {
	    public int size() { return GC_THRESHOLD; }
	    public int memoryConsumed() { return pos_; }
	    public VM_Address getBaseAddress() {
		return memory_;
	    }
	    public VM_Address getMem(int size) {
		return S3HalfSpaceAllocator.this.getMem(size);
	    }
            public String toString()throws PragmaNoPollcheck { return "Heap Area"; }
	    public Oop revive(Destructor d) {
		if (scanner != null) {
		    Oop doop = VM_Address.fromObject(d).asOop();
		    VM_Address ref = destructorRef.addressWithin(doop);
		    VM_Address val = ref.getAddress();
		    if (val.isNonNull() && inFromSpace(val)) {
			if(!(((MovingGC)val.asAnyOop()).isForwarded())){
			    vmint.moveObject(val.asOop(), val.asOop().getBlueprint());
			    scanner.updateExtent(memory_.add((half_*HALF_SPACE_SIZE)+pos_));
			}
			ref.setAddress(((MovingGC)val.asAnyOop())
				       .getForwardAddress());
		    }
		}
		return super.revive(d);
	    }
		
	};
    public VM_Area getHeapArea() { return heapArea; }
    public boolean supportsDestructors() { return true; }
} // end of S3Allocator
