/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package tools.mips;

import API.Utils;
import java.util.TreeMap;

/**
 *
 * @author EUGENIO CARVALHO
 */
public class Memory {

    public Integer WORD_BYTES_COUNT = 4;
    protected String EMPTY_BYTE = "00000000";
    protected boolean writable;
    protected String file;
    protected Long size = 0L, localAddress = 0L;
    protected TreeMap<Long, String> data = new TreeMap<>();
    protected MemoryImportExportInterface io;

    public Memory ReadOnly() {
        this.writable = false;
        return this;
    }

    public int WordBitSize() {
        return WORD_BYTES_COUNT * 8;
    }

    public Memory Writable() {
        this.writable = true;
        return this;
    }

    public void Reset() {
        data.entrySet().forEach((entry) -> {
            entry.setValue(EMPTY_BYTE);
        });
    }

    public Memory(long size) {
        this.size = size;
    }

    public Memory(String filename, long size) throws Exception {
        if (filename.equals("")) {
            throw new Exception("Empty file not allowed.");
        }
        this.file = filename;
        this.size = size;
    }

    public TreeMap<Long, String> Data() {
        return data;
    }

    @Override
    public String toString() {
        try {
            return Dump();
        } catch (Exception ex) {
            return ex.getMessage();
        }
    }

    protected void CheckIOSet() throws Exception {
        if (io == null) {
            throw new Exception("Interface de IO da memória não definida.");
        }
    }

    public String Dump() throws Exception {
        CheckIOSet();
        return io.DumpFile(this);
    }

    protected void ReadFile() throws Exception {
        CheckIOSet();
        io.FromFile(this);
    }

    public Long ReadLong(long address, int rsize) throws Exception {
//        System.out.println("R(address, rsize):" + Utils.padPreserveSignal(64, R(address, rsize)));
        return Long.parseUnsignedLong(Utils.padPreserveSignal(64, R(address, rsize)), 2);
    }

    public String R(long address, int rsize) throws Exception {
        valid(address);
        String buffer = "";
        for (int i = 0; i < rsize; i++) {
            buffer += RB(address + i);
        }
        return buffer;
//        return Utils.Pad(buffer, "0", Utils.PAD_LEFT, 32);
//        System.out.println("R:" + buffer);
//        return Utils.padPreserveSignal(32, buffer);
    }

    public String RB(long address) throws Exception {
        valid(address);
        address = (address + size) % size;
        if (!data.containsKey(address)) {
            data.put(address, EMPTY_BYTE);
        }
        return data.get(address);
    }

    protected void valid(long address) throws Exception {
        if (Math.abs(address) > size) {
            throw new Exception("Address '" + address + "' out of range limit " + size + "!");
        }
    }

    public void WB(String sbyte) throws Exception {
        WB(localAddress++, sbyte);
    }

    public void WB(Long address, String sbyte) throws Exception {
        valid(address);

        int len = sbyte.length();

        if (len > 8) {
            sbyte = sbyte.substring(sbyte.length() - 8);
        } else if (len < 8) {
            sbyte = Utils.Pad(sbyte, "0", Utils.PAD_LEFT, 8);
        }

        data.put((address + size) % size, sbyte);
    }

    public void W(long address, long value, int wsize) throws Exception {
        W(address, Long.toBinaryString(value), wsize);
    }

    public void W(long address, String bin, int wsize) throws Exception {
        valid(address);
        int bpos = 4 - wsize;

        bin = Utils.Pad(bin, "0", Utils.PAD_LEFT, 32);

        String[] bytes = Utils.SplitEach(bin, 8);
        //Fix to 16 bytes of negative value binary

//        System.out.println("W(" + address + ")" + Arrays.asList(bytes));
        if (bytes.length > 4) {
            bpos += 4;
        }

        for (int i = 0; i < wsize; i++) {
            WB(address + i, bytes[bpos + i]);
        }
    }

    public Memory Save() throws Exception {
        return Save(file);
    }

    public Memory Save(String filename) throws Exception {
        // Grava o conteudo no arquivo
        Utils.WriteFile(filename, Dump());
        return this;
    }

    public void SetIO(MemoryImportExportInterface memoryInitializationFile) {
        io = memoryInitializationFile;
    }

    public String File() {
        return file;
    }

    public long Size() {
        return size;
    }

}
