package fabrik.simulator.pic16f84;


import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

import fabrik.simulator.pic16f84.interfaces.*;

public class EEPROM extends PICComponent implements EEPROMInterface, ExecutionTimeObserver {
    private final int EEDATA = 0x08;
    private final int EEADR = 0x09;
    private final int EECON1 = 0x88;
    private final int EECON2 = 0x89;

    private final int RD = 0x00;
    private final int WR = 0x01;
    private final int WRERR = 0x03;
    private final int EEIF = 0x04;

    private boolean readControl = false;
    private boolean writeControl = false;
    private boolean [] eecon2stages = {false, false};


    private double startTime;
    
    public EEPROM (){
    }

    public long read (int address) {
        if (address < 0) return 0;
        FileReader reader;
        try {
            reader = new FileReader("eeprom.json");
        } catch (FileNotFoundException e) {
            return 0;
        }
        JSONParser parser = new JSONParser();
        JSONObject data;
        try {
            data = (JSONObject) parser.parse(reader);
        } catch (ParseException | IOException e) {
            return 0;
        }
        Object requestedData = data.get(String.valueOf(address));
        try {
            readControl = false;
            dataRegister.setDirectBit(EECON1, RD, 0);
            return (long) requestedData;
        } catch (NullPointerException e) {
            return 0;
        }
    }

    public void write (int address, long data) {
        if (address < 0) return;
        FileReader reader;
        try {
            reader = new FileReader("eeprom.json");
        } catch (FileNotFoundException ignored) {
            dataRegister.setDirectBit(EECON1, WRERR, 1);
            return;
        }
        JSONParser parser = new JSONParser();
        JSONObject eeprom;
        try {
            eeprom = (JSONObject) parser.parse(reader);
        } catch (ParseException | IOException ignored) {
            dataRegister.setDirectBit(EECON1, WRERR, 1);
            return;
        }
        eeprom.put(String.valueOf(address), data);
        try {
            Files.write(Paths.get("eeprom.json"), eeprom.toJSONString().getBytes());
        } catch (IOException ignored) {
            dataRegister.setDirectBit(EECON1, WRERR, 1);
            return;
        }
        registerTime(executionTime.getTotalExecutionTime(), true);
    }

    public void registerTime(double executionTime, boolean reset) {
        if (reset)
            startTime = executionTime;
        else if ((executionTime >= (startTime + 1000)) && writeControl) {
            eecon2stages = new boolean[]{false, false};
            dataRegister.setDirectBit(EECON1, EEIF, 1);
            dataRegister.setDirectBit(EECON1, WR, 0);
            writeControl = false;
            startTime = 0;
        }
    }

    public void parse(int address, int content, int opcode) {
        //OPCODE: 0b1x = set
        //OPCODE: 0b0x = clear
        //OPCODE: 0bx1 = byte
        //OPCODE: 0bx0 = bit
        switch (opcode){
            case 0b00: // CLEAR BIT
                if(address == EECON1) {
                    if (dataRegister.getDirectBit(address, content) == 1)
                        setEECON1((int) (dataRegister.getDirectRegister(EECON1) - Math.pow(2, content)));
                } else {
                    dataRegister.setDirectBit(address, content, 0);
                }
                break;
            case 0b10: // SET BIT
                if(address == EECON1) {
                    if (dataRegister.getDirectBit(address, content) == 0)
                        setEECON1((int) (dataRegister.getDirectRegister(EECON1) + Math.pow(2, content)));
                } else {
                    if (address == EEADR && content >= 0b01000000)
                        dataRegister.setDirectBit(address, content, 0);
                    else
                        dataRegister.setDirectBit(address, content, 1);
                }
                break;
            case 0b11: // SET BYTE
                if(address == EECON1) {
                    setEECON1 (content);
                } else {
                    if (address == EEADR)
                        content &= 0b00111111;
                    else if (address == EECON2) {
                        if (content == 0x55)
                            eecon2stages [0] = true;
                        else if (content == 0xAA && eecon2stages[0])
                            eecon2stages [1] = true;
                        else
                            eecon2stages = new boolean[]{false, false};
                    }
                    dataRegister.setDirectRegister(address, content);
                }
                break;
        }
    }

    private void setEECON1(int content) {
        content &= 0b00011111;
        boolean writeEnabled = ((content & 0b100) >> 2) == 1;
        if ((content & 0b1) == 1) { // READ
            readControl = true;
            dataRegister.setDirectRegister(EECON1, content);
            int data = (int) read(dataRegister.getDirectRegister(EEADR));
            dataRegister.setDirectRegister(EEDATA, data);
            return;
        }
        else if ((content & 0b1) == 0 && readControl) // RD kann nicht manuell gecleart werden
            content |= 0b1;
        if (((content & 0b10) >> 1) == 1) { // WRITE CONTROL
            if (writeEnabled && eecon2stages[0] && eecon2stages[1]) {
                writeControl = true;
                dataRegister.setDirectRegister(EECON1, content);
                write(dataRegister.getDirectRegister(EEADR), dataRegister.getDirectRegister(EEDATA));
                return;
            }
            else {
                content -= 0b10;
            }
        }
        else if ((((content & 0b10) >> 1) == 0) && writeControl) // WR kann nicht manuell gecleart werden
            content |= 0b10;
        dataRegister.setDirectRegister(EECON1, content);
    }

    @Override
    public void initialize(PICComponentLocator locator) {
        super.initialize(locator);
        executionTime.registerObserver(this);
    }

    @Override
    public void executionTimeChanged() {
        registerTime(executionTime.getTotalExecutionTime(), false);
    }
}
