
package cz.karry.dop.memory;

import cz.karry.dop.core.Bus;
import cz.karry.dop.core.BusListener;
import cz.karry.dop.core.Core;
import cz.karry.dop.core.Signal;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author karry
 */
public class Memory implements cz.karry.dop.core.Memory, cz.karry.dop.core.SignalListener, cz.karry.dop.core.BusListener{


    
    final int T_WAIT = 3; // wait clock count

    
    private Core core;
    private int address = -1;
    private int data[] = new int[0];
    private boolean readRequest = false;
    private boolean writeRequest = false;
    private int counter=0;
    private cz.karry.dop.Logger logger;
    public static final int MIN_VALUE = 0;
    public static final int MAX_VALUE = 0xff;

    private ArrayList<MemoryChangeListener> listeners = new ArrayList<MemoryChangeListener>();
    
    /**
     * Create new memory and try parse memoryData to content
     * 
     * @param memoryData
     * @param logger
     */
    public Memory(String memoryData, cz.karry.dop.Logger logger) {
        this.logger = logger;
        String[] line = memoryData.split("\n");
        data = new int[line.length];
        String str;
        for (int i=0; i<line.length;i++){
            str = line[i].trim();
            if (str.length()==0){
                logger.addError("can not parse line "+i);
                data[i] = 0;
            }else{
                try{
                    setValue(i, Memory.parseNumber(str));
                }catch(NumberFormatException ex){
                    logger.addError("can not parse line "+i+" (NumberFormatException: "+ex.getMessage()+")");
                }catch(Exception ex){
                    logger.addError("can not parse line "+i+" (Exception: "+ex.getMessage()+")");
                }
            }            
        }
    }
    
    /**
     * Creste empty memory with specified size.
     * 
     * @param size      size of the memory
     * @param logger    
     */
    Memory(int size, cz.karry.dop.Logger logger){
        if (size>0){
            data = new int[size];
        }
        this.logger = logger;
    }

    public void addChangeLisener(MemoryChangeListener listener) {
        listeners.add( listener );
    }
        
    public int[] getData(){
        return data;
    }

    
    private static int parseNumber(String str){
        str = str.trim();

        if ( (str.length()>=3) && (str.substring(0,2).toLowerCase().equals("0x"))){
            return Integer.valueOf(str.substring(2), 16);
        }else{
            return Integer.valueOf(str, 10);
        }
    }
    
    public void print() {
        String text = "";
        text = "memory size: "+data.length+" (x8 bit)\n";
        for (int i=0; i<data.length; i++){
            text += i+": 0x"+Integer.toHexString(data[i])+"\n";
        }
        if (logger==null){
            System.out.println(text);
        }else{
            logger.addMessage(text);
        }
    }
    
    public void setData(int[] data){
        this.data = data;
    }
    
    public int getValue(int index){
        if (index>=0 && index<data.length){
            logger.addMessage("memory: read data from 0x"+Integer.toHexString(index)+" (0x"+Integer.toHexString(this.data[index])+")");
            return this.data[index];
        }else{
            logger.addError("Memory address ("+index+") out of range (memory size is "+data.length+")");
            return 0;
        }        
    }
            
    public void setValue(int index, int value){
        if (index>=0 && index<data.length){
            if (value<MIN_VALUE){
                value = MIN_VALUE;
                logger.addError("memory: value can not be negative!");
            }
            if (value>MAX_VALUE){
                value = MAX_VALUE;
                logger.addError("memory: value can not be greater that "+MAX_VALUE+"!");
            }
            
            if (value != this.data[index]){
                logger.addMessage("memory: set data on 0x"+Integer.toHexString(index)+" to 0x"+Integer.toHexString(value));
                this.data[index] = value;
                for (int i=0; i<listeners.size(); i++){
                    listeners.get(i).memoryChangePerformed(index, value);
                }
            }
        }else{
            logger.addError("Memory address ("+index+") out of range (memory size is "+data.length+")");
        }        
        
    }
            
    public void connectCore(final Core core) {
        this.core = core;
        try {
            core.getSignal( Core.CLK ).addSignalListener(this);
            core.getSignal( Core.MRD ).addSignalListener(this);
            core.getSignal( Core.MWR ).addSignalListener(this);
        } catch (Exception ex) {
            Logger.getLogger(Memory.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        core.ab_bus.addBusListener( this );
    }

    public void signalChangePerformed(Signal signal, boolean active) {
        try {
            if (signal.getName().equals(Core.CLK)){

                    if (active){    // rising edge of clock
                        if (core.getSignal(Core.MRD).isActive() && !readRequest) {

                        }else if (core.getSignal(Core.MWR).isActive() && !writeRequest) {

                        }
                        if (!core.getSignal(Core.MRD).isActive() && readRequest){  // cancel read 
                            readRequest = false;
                            core.getSignal(Core.WAIT).setState(false);
                            logger.addMessage("memory: stop (cancel) read");
                            core.db_bus.setValue(0xff);
                        }
                        if (!core.getSignal(Core.MWR).isActive() && writeRequest){ // cancel write
                            writeRequest = false;
                            core.getSignal(Core.WAIT).setState(false);
                            logger.addMessage("memory: stop (cancel) write");
                        }
                    }else{      // faling edge of clock
                        if (writeRequest){
                            logger.addMessage("memory: couter value="+counter);
                            counter--;
                            if (counter<=0){
                                setValue(address, core.db_bus.getValue());
                                core.getSignal(Core.WAIT).setState(false);                                
                            }
                        }
                        if (readRequest){
                            logger.addMessage("memory: couter value="+counter);
                            counter--;
                            if (counter<=0){
                                core.db_bus.setValue(getValue(address));
                                core.getSignal(Core.WAIT).setState(false);
                            }
                        }
                    }
            }

            if ( signal.getName().equals( Core.MRD ) && active && !readRequest){
                            readRequest = true;
                            counter = T_WAIT;
                            address = core.ab_bus.getValue();
                            core.getSignal(Core.WAIT).setState(true);
                            logger.addMessage("memory: readRequest");            
            }

            if ( signal.getName().equals( Core.MWR ) && active && !writeRequest){
                            writeRequest = true;
                            counter = T_WAIT;
                            address = core.ab_bus.getValue();
                            core.getSignal(Core.WAIT).setState(true);
                            logger.addMessage("memory: writeRequest");
            }
        } catch (Exception ex) {
            Logger.getLogger(Memory.class.getName()).log(Level.SEVERE, null, ex);
        }
        
    }

    public void disconnectCore() {
        try {
            core.getSignal( Core.CLK ).removeSignalListener(this);
            core.getSignal( Core.MRD ).removeSignalListener(this);
            core.getSignal( Core.MWR ).removeSignalListener(this);
        } catch (Exception ex) {
            Logger.getLogger(Memory.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        core.ab_bus.removeBusListener( this );
    }

    public void busChangePerformed(Bus bus, int value) {
        if (bus.getName().equals(Core.AB_BUS)){
            address = value;            
        }
    }

}
