var PinAlreadyConnected = Class("PinAlreadyConnected", Error, function(thisClass, Super){
    thisClass.prototype.init = function(pin){
        this.message = "The pin has already been connected to a line or another pin.";
        this.pin = pin;
    }
    thisClass.prototype.toString = function() {
        return "[Error: " + this.constructor + "]\n\n" + this.message;
    }
})


//-------------------------------events-----------------------------------------
var ChangedEvent = Class("ChangedEvent", function(thisClass, Super){
    thisClass.prototype.init=function(source){
        this.type = "changed";
        this.source = source;
    }
})

//--------------------------------circuits-------------------------------------------
var SimpleCircuit = Class("SimpleCircuit", function(thisClass, Super){
    thisClass.prototype.init = function(){
        this.listeners = new Array();
    }
    
    thisClass.prototype.pinChanged = function(pin){
    
    }
    
    thisClass.prototype.addListener = function(listener){
        this.listeners.push(listener)
    }
    
    thisClass.prototype.fireEvent = function(evt){
        for(var i=0;i<this.listeners.length;i++){
            try{
                this.listeners[i].handleEvent(evt);
            }catch(e){
                //todo
            }
        }
    }
})

//----------------------------------------------------------------------------------------------------------------------------------------
var Line = Class("Line", SimpleCircuit, function(thisClass, Super){
   
    thisClass.prototype.init = function(lineOrPin){
        Super.prototype.init.call(this);
        this.pinsOrLines = new Array();
        this.value = false;
        this.recursioncheck = false;
        for(var i=0;i<arguments.length;i++){
            this.connect(arguments[i]);
        }
    }
    
    thisClass.prototype.setValue = function(value){
        if (this.value != value){
            this.value = value;
            this.fireEvent(new ChangedEvent(this));
            //dispatch the value to the other pins/lines
            for(var i=0;i<this.pinsOrLines.length;i++){
                try{
                    this.pinsOrLines[i].pinChanged(this);
                }catch(e){
                }
            }
        }
    }
    
    thisClass.prototype.pinChanged = function(lineOrPin){
        if (this.recursioncheck==false){
            this.recursioncheck = true;
            if (this.value != lineOrPin.value){
                this.value = lineOrPin.value;
                this.fireEvent(new ChangedEvent(this));
                //dispatch the value to the other pins/lines
                for(var i=0;i<this.pinsOrLines.length;i++){
                    if(this.pinsOrLines[i] != lineOrPin){
                        try{
                            this.pinsOrLines[i].pinChanged(this);
                        }catch(e){
                        }
                    }    
                }
            }
            this.recursioncheck = false;
        }
    }
     
    thisClass.prototype.connect = function(lineOrPin){
        if(lineOrPin){
            for(var i=0;i<this.pinsOrLines.length;i++){
                if(this.pinsOrLines[i] == lineOrPin){
                    return;
                }
            }
            this.pinsOrLines.push(lineOrPin);
            lineOrPin.connect(this);
        }
    }
})

//----------------------------------------------------------------------------------------------------------------------------------------
var Pin = Class("Pin", SimpleCircuit, function(thisClass, Super){

    thisClass.prototype.init = function(owner, outPut){
        Super.prototype.init.call(this);
        this.owner = owner;
        this.line = null;
        if(outPut == null){
            this.isInPin = true;
            this.value = false;
        }else{
            this.isInPin = false;
            this.value = outPut;
        }
    }

    thisClass.prototype.setValue = function(value){
        if(this.value != value){
            this.value = value;
            this.fireEvent(new ChangedEvent(this));
            //dispatch the value to the connected pin or line
            try{
                this.lineOrPin.pinChanged(this);
            }catch(e){
                //todo
            }
        }
    }
    
    thisClass.prototype.pinChanged = function(lineOrPin){
        if(this.isInPin){
            if(this.value != lineOrPin.value){
                this.value = lineOrPin.value;
                this.fireEvent(new ChangedEvent(this));
                //dispatch the value to the connected pin or line
                try{
                    this.owner.pinChanged(this);
                }catch(e){
                    //todo
                }
            }
        }
    }
    
    thisClass.prototype.connect = function(lineOrPin){
        if(lineOrPin){
            if(this.lineOrPin != lineOrPin){
                if(this.lineOrPin == null){
                    this.lineOrPin = lineOrPin;
                    if(this.isInPin){
                        this.pinChanged(lineOrPin);
                    }else{
                        lineOrPin.pinChanged(this);
                    }
                }else{
                    throw new PinAlreadyConnected(this);
                }
            }
        }
    }
})
  
  

//----------------------------------------------------------------------------------------------------------------------------------------
var Circuit = Class("Circuit", SimpleCircuit, function(thisClass, Super){

    thisClass.prototype.init = function(delay){
        Super.prototype.init.call(this);
        this.delay = delay ? delay : 1;
    }
    
    thisClass.prototype.addPin = function(name, putOut){
        var pin = new Pin(this, putOut);
        this[name] = pin;
        return pin;
    }
    
    thisClass.prototype.pinChanged = function(pin){
        if(pin.isInPin){
            simulator.invokeLater(this, this.updateState, this.delay);
        }
    }
})

//----------------------------------------------------------------------------------------------------------------------------------------
var Clock = Class("Clock", Circuit, function(thisClass, Super){

    thisClass.prototype.init = function(delay){
        Super.prototype.init.call(this, delay);
        this.value = false;
        this.addPin("out0", false);
        this.addPin("out1", true);
        this.stoped = true;
    }
    
    thisClass.prototype.start = function(){
        if(this.stoped){
            this.stoped = false;
            this.toggleState();
        }
    }
    
    thisClass.prototype.stop = function(){
        this.stoped = true;
    }
    
    thisClass.prototype.step = function(){
        this.stoped = true;
        this.value = ! this.value;
        this.updateState();
        simulator.run();
    }
    
    thisClass.prototype.toggleState = function(){
        if(this.stoped == false){
            this.value = ! this.value;
            this.updateState();
            scheduler.register(this, this.toggleState, this.delay);
            //todo
            simulator.run();
        }
    }
    
    thisClass.prototype.updateState = function(){
        this.out0.setValue(this.value);
        this.out1.setValue(!this.value);
        this.fireEvent(new ChangedEvent(this));
    } 
})

//----------------------------------------------------------------------------------------------------------------------------------------
var NotGate = Class("NotGate", Circuit, function(thisClass, Super){

    thisClass.prototype.init = function(){
        Super.prototype.init.call(this, 1);
        this.addPin("in0");
        this.addPin("out", true);
    }
    
    thisClass.prototype.updateState = function(){
        if (this.in0.value){
            this.out.setValue(false);
        }else{
            this.out.setValue(true);
        }
        //todo: this.fireEvent(new ChangedEvent(this));
    }
})

//----------------------------------------------------------------------------------------------------------------------------------------
var AndGate = Class("AndGate", Circuit, function(thisClass, Super){

    thisClass.prototype.init = function(){
        Super.prototype.init.call(this, 1);
        this.addPin("in0");
        this.addPin("in1");
        this.addPin("out", false);
    }
    
    
    thisClass.prototype.updateState = function(){
        this.out.setValue(this.in0.value && this.in1.value);
        //todo: this.fireEvent(new ChangedEvent(this));
    }
})

//----------------------------------------------------------------------------------------------------------------------------------------
var OrGate = Class("OrGate", Circuit,function(thisClass, Super){

    thisClass.prototype.init = function(){
        Super.prototype.init.call(this, 1);
        this.addPin("in0");
        this.addPin("in1");
        this.addPin("out", false);
    }
    
    thisClass.prototype.updateState = function(){
        this.out.setValue(this.in0.value || this.in1.value);
        //todo: this.fireEvent(new ChangedEvent(this));
    }
})

//----------------------------------------------------------------------------------------------------------------------------------------
var GateGate = Class("GateGate", Circuit, function(thisClass, Super){

    thisClass.prototype.init = function(size){
        Super.prototype.init.call(this, 1);
        this.size = size;
        this.addPin("gateKeeper");
        this.inPins = new Array();
        for(var i=0;i<size;i++){
            var pin = this.addPin("in" + i);
            this.inPins.push(pin);
        }
        this.outPins = new Array();
        for(var i=0;i<size;i++){
            var pin = this.addPin("out" + i, false);
            this.outPins.push(pin);
        }
    }
        
    thisClass.prototype.updateState = function(){
        if (this.gateKeeper.value){
            for(var i=0;i<this.size;i++){
                this.outPins[i].setValue(this.inPins[i].value);
            }
        }else{
            for(var i=0;i<this.size;i++){
                this.outPins[i].setValue(false);
            }
        }
        this.fireEvent(new ChangedEvent(this));
    }
  })

//----------------------------------------------------------------------------------------------------------------------------------------
var BusJoin = Class("BusJoin", GateGate, function(thisClass, Super){

    thisClass.prototype.init = function(size){
        Super.prototype.init.call(this, size);
        this.busPins = new Array();
        for(var i=0;i<this.size;i++){
            var pin = this.addPin("bus" + i);
            this.busPins.push(pin);
        }
    }
    
    thisClass.prototype.updateState = function(){
        if (this.gateKeeper.value){
            for(var i=0;i<this.size;i++){
                this.outPins[i].setValue(this.inPins[i].value || this.busPins[i].value);
            }
        }else{
            for(var i=0;i<this.size;i++){
                this.outPins[i].setValue(this.busPins[i].value);
            }
        }
        this.fireEvent(new ChangedEvent(this));
    }
})

//----------------------------------------------------------------------------------------------------------------------------------------
var Register = Class("Register", GateGate, function(thisClass, Super){

    thisClass.prototype.init = function(size){
        Super.prototype.init.call(this, size);
        this.assignmentTrigger = this.gateKeeper;
        this.value = new Array();
        for(var i=0;i<size;i++){
            this.value.push(false);
        }
    }
    
    thisClass.prototype.updateState = function(){
        if (this.assignmentTrigger.value){
            for(var i=0;i<this.inPins.length;i++){
                this.value[i] = this.inPins[i].value;
            }
        }
        for(var i=0;i<this.outPins.length;i++){
            this.outPins[i].setValue(this.value[i]);
        }
        this.fireEvent(new ChangedEvent(this));
    }
})

//----------------------------------------------------------------------------------------------------------------------------------------
var Rom = Class("Rom", Circuit, function(thisClass, Super){
    thisClass.prototype.init = function(addrWidth, valueWidth, content){
        Super.prototype.init.call(this);
        this.addrWidth = addrWidth;
        this.valueWidth = valueWidth;
        this.content = content.replace(/\s/g,"")
        this.content = this.content.split(";");
       
        this.inPins = new Array();
        this.outPins = new Array();
        for(var i=0;i<addrWidth;i++){
            var pin  = this.addPin("addr" + i);
            this.inPins.push(pin);
        }
        
        var a =  this.content[0].split(",");
        for(var i=0;i<valueWidth;i++){
            var pin  = this.addPin("out" + i, (a[(this.valueWidth-1) - i] == "1"));
            this.outPins.push(pin);
        }
        
    }
        
    thisClass.prototype.updateState = function(){
        var addr = this.getAddr();
        try{
            var a =  this.content[addr].split(",");
            for(var i=0;i<this.outPins.length;i++){
                this.outPins[i].setValue((a[(this.valueWidth-1) - i] == "1"));
            }
        }catch(e){
        }
    }
    
    thisClass.prototype.getAddr = function(){
        var a=0;
        for(var i=0;i<this.addrWidth;i++){
            a += (this.inPins[i].value ? 1 : 0)  * Math.pow(2,i);
        }
        return a;
    }
})

//----------------------------------------------------------------------------------------------------------------------------------------

var Selection = Class("Selection", Circuit, function(thisClass, Super){

    thisClass.prototype.init = function(){
        Super.prototype.init.call(this);
        
        this.inPins = new Array();
        for(var i=0;i<7;i++){
            var pin  = this.addPin("test" + i);
            this.inPins.push(pin);
        }
        for(var i=0;i<3;i++){
            var pin  = this.addPin("case" + i);
            this.inPins.push(pin);
        }
        this.addPin("outElse", true);
        this.addPin("outThen", false);
        this.addPin("outAddr", false);
    }
       
    thisClass.prototype.updateState = function(){
        var n = bin2Int(new Array(this.case0.value, this.case1.value, this.case2.value));
        if(n==7){
            this.outElse.setValue(0);
            this.outAddr.setValue(1);
            this.outThen.setValue(0);
        }else if(this["test" + n].value){
            this.outElse.setValue(0);
            this.outAddr.setValue(0);
            this.outThen.setValue(1);
        }else{
            this.outElse.setValue(1);
            this.outAddr.setValue(0);
            this.outThen.setValue(0);
        }
    }
});


var Microspatz1321 = Class("Microspatz1321", Circuit, function(thisClass, Super){
    thisClass.prototype.init = function(speed, romContend){
        Super.prototype.init.call(this);
        
        this.rom = new Rom(6, 36, romContend);
        this.addrReg = new Register(6);
        this.instrReg = new Register(36);
        this.clock = new Clock(speed);
        this.elseGate = new GateGate(6);
        this.thenBusJoin = new BusJoin(6);
        this.addrBusJoin = new BusJoin(6);
        this.selection = new Selection();
        this.pulseGate = new GateGate(9);
        
        //rom -> instrReg
        for(var i=0;i<36;i++){
            var l = new Line(this.rom["out" + i], this.instrReg["in" + i]);
            this["rom2instrReg" + i] = l;
        }
        
        //clock -> instrReg, addrReg, pulseGate
        this.clock2pulse = new Line(this.clock.out0, this.pulseGate.gateKeeper);
        this.clock2addr = new Line(this.clock2pulse, this.addrReg.assignmentTrigger);
        this.clock2instrReg = new Line(this.clock.out1, this.instrReg.assignmentTrigger);
        
        //instrReg -> pulseGate 
        for(var i=0;i<9;i++){
            this["instrReg2pulseGate" + i] = new Line(this.pulseGate["in" + i], this.instrReg["out" + i]);
        }
        
        //pulseGate -> pulsePins 
        for(var i=0;i<=8;i++){
            var l = new Line(this.pulseGate["out" + i]);
            this["pulseGate2pulsePin" + i] = l; 
            var p = this.addPin("pulse" + i, false);
            p.connect(l);
        }
        
        //instrReg -> switchPins
        for(var i=9;i<=20;i++){
            var l = new Line(this.instrReg["out" + i]);
            this["instrReg2switchPin" + i] = l;
            var p = this.addPin("switch" + i, false);
            p.connect(l);
        }
        
        //else -> elseGate
        for(var i=0;i<6;i++){
            this["else2elseGate" + i] = new Line(this.instrReg["out" + (i + 21)], this.elseGate["in" + i]);
        }
        
        //then -> thenBusJoin
        for(var i=0;i<6;i++){
            this["then2thenBusJoin" + i] = new Line(this.instrReg["out" + (i + 27)], this.thenBusJoin["in" + i]);
        }
                
        //case -> selection
        this.case2selection0 = new Line(this.instrReg.out33, this.selection.case0);
        this.case2selection1 = new Line(this.instrReg.out34, this.selection.case1);
        this.case2selection2 = new Line(this.instrReg.out35, this.selection.case2);
        
        //inAddrPins -> addrBusJoin
        for(var i=0;i<6;i++){
            var l = new Line(this.addrBusJoin["in" + i]);
            this["inAddrPins2addrBusJoin" + i] = l;
            var p = this.addPin("inAddr" + i);
            p.connect(l);
        }
        
        //elseGate -> addrBusJoin
        for(var i=0;i<6;i++){
            this["elseGate2addrBusJoin" + i] = new Line(this.elseGate["out" + i], this.addrBusJoin["bus" + i]);
        }
        
        //addrBusJoin -> thenBusJoin
        for(var i=0;i<6;i++){
            this["addrBusJoin2thenBusJoin" + i] = new Line(this.addrBusJoin["out" + i], this.thenBusJoin["bus" + i]);
        }
        
        //thenBusJoin -> addrReg
        for(var i=0;i<6;i++){
            this["thenBusJoin2addrReg" + i] = new Line(this.thenBusJoin["out" + i], this.addrReg["in" + i]);
        }
        
        //addrReg -> rom
        for(var i=0;i<6;i++){
            this["addrReg2rom" + i] = new Line(this.addrReg["out" + i], this.rom["addr" + i]);
        }
        
        
        //selection -> thenBusJoin, elseGate, addrBusJoin
        this.selection2thenBusJoin = new Line(this.selection.outThen, this.thenBusJoin.gateKeeper);
        this.selection2elseGate = new Line(this.selection.outElse, this.elseGate.gateKeeper);
        this.selection2addrBusJoin = new Line(this.selection.outAddr, this.addrBusJoin.gateKeeper);
        
        //selection -> testPins
        for(var i=0;i<=6;i++){
            var l = new Line(this.selection["test" + i]);
            this["selection2testPin" + i] = l;
            var p = this.addPin("test" + i);
            p.connect(l);
        }

    }
    
   // thisClass.prototype.pinChanged = function(pin){
    
   // }
})


var bin2Int=function(binArray){
    var n=0;
    for(var i=0; i<binArray.length; i++){
        n += (binArray[i] ? 1 : 0)  * Math.pow(2,i);
    }
    return n;
}






