/**
 * Copyright (c) 2004-2005, Regents of the University of California
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * Neither the name of the University of California, Los Angeles nor the
 * names of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package avrora.sim.mcu;

import avrora.sim.RWRegister;
import avrora.sim.Simulator;
import avrora.sim.state.BooleanView;
import avrora.sim.state.RegisterUtil;
import cck.util.Arithmetic;
import cck.text.StringUtil;

/**
 * This is an implementation of the non-volatile EEPROM on the ATMega128 microcontroller.
 *
 * @author Daniel Lee
 * @author Sascha Silbe
 */
public class EEPROM extends AtmelInternalDevice {

    final int EEPROM_SIZE, EEPROM_SIZE_numBits;

    public static final int EEARH = 0x1F;
    public static final int EEARL = 0x1E;
    public static final int EEDR = 0x1D;
    public static final int EECR = 0x1C;

    final byte[] EEPROM_data;
    final RWRegister EEDR_reg;
    final EECRReg EECR_reg;
    final RWRegister EEARL_reg;
    final EEARHReg EEARH_reg;

    // flag bits on EECR
    static final int EERIE = 3;
    static final int EEMWE = 2;
    static final int EEWE = 1;
    static final int EERE = 0;

    static final int EEPROM_INTERRUPT = 23;

    boolean masterWriteEnable;
    boolean writeEnable;
    boolean readEnable;

    final EEPROMTicker ticker;
    final EEPROMWriteFinishedEvent writeFinishedEvent;

    int writeCount = -1;
    // TODO: move these flags to use BooleanView for consistency.
    boolean writeEnableWritten;
    boolean readEnableWritten;

    EEPROM(int size, AtmelMicrocontroller m) {
        super("eeprom", m);

        ticker = new EEPROMTicker();
        writeFinishedEvent = new EEPROMWriteFinishedEvent();

        EEDR_reg = new RWRegister();
        EECR_reg = new EECRReg();
        EEARL_reg = new EEARLReg();
        EEARH_reg = new EEARHReg();

        EEPROM_SIZE = size;
        EEPROM_SIZE_numBits = Arithmetic.log(size);
        //EEPROM_SIZE_numBits = new BigInteger(Integer.toString(size)).bitLength();
        EEPROM_data = new byte[EEPROM_SIZE];

        installIOReg("EEDR", EEDR_reg);
        installIOReg("EECR", EECR_reg);
        installIOReg("EEARL", EEARL_reg);
        installIOReg("EEARH", EEARH_reg);
    }

    public int getSize() {
        return EEPROM_SIZE;
    }

    public void setContent(byte[] contents) {
        for (int addr = 0; addr < contents.length; addr++) {
            EEPROM_data[addr] = contents[addr];
        }
 //       if (devicePrinter != null) devicePrinter.println("EEPROM: content set");
    }

    public byte[] getContent() {
        return EEPROM_data;
    }

    protected class EEARHReg extends RWRegister {

        public void write(byte val) {
            // EEAR access not allowed during write
            if (writeEnable) return;

            value = (byte)(val & ((EEPROM_SIZE >> 8) - 1));
        }

    }

    protected class EEARLReg extends RWRegister {

        public void write(byte val) {
            // EEAR access not allowed during write
            if (writeEnable) return;

            value = (byte)(val & Math.min(EEPROM_SIZE - 1, 255));
        }
    }

    protected class EECRReg extends RWRegister {

        final BooleanView _eerie = RegisterUtil.booleanView(this, EERIE);
        final BooleanView _eere = RegisterUtil.booleanView(this, EERE);
        final BooleanView _eemwe = RegisterUtil.booleanView(this, EEMWE);
        final BooleanView _eewe = RegisterUtil.booleanView(this, EEWE);

        public void decode() {
            // TODO: move these flags to boolean views (and store old register value)
            if (newTrue(readEnable, readEnable = _eere.getValue())) {
  //              if (devicePrinter != null) devicePrinter.println("EEPROM: EERE flagged");
                readEnableWritten = true;
            }
            if (newTrue(writeEnable, writeEnable = _eewe.getValue())) {
  //              if (devicePrinter != null) devicePrinter.println("EEPROM: EEWE flagged");
                writeEnableWritten = true;
            }
            if (newTrue(masterWriteEnable, masterWriteEnable = _eemwe.getValue())) {
                // EEMWE has been written to. reset write count
    //            if (devicePrinter != null) devicePrinter.println("EEPROM: reset write count to 4");
                writeCount = 4;
            }
            interpreter.setEnabled(EEPROM_INTERRUPT, _eerie.getValue());
            interpreter.setPosted(EEPROM_INTERRUPT, !writeEnable);
            mainClock.insertEvent(ticker, 1);
        }

        public void write(byte val) {
            value = (byte)(0xf & val);
            if (devicePrinter != null) {
    //            devicePrinter.println("EEPROM: EECR written to, val = " + StringUtil.toBin(value, 4));
            }
            decode();
        }

        private boolean newTrue(boolean b1, boolean b2) {
            return !b1 && b2;
        }

        public void resetEERE() {
            _eere.setValue(false);
            decode(); // TODO: perform only relevant checks.
        }

        public void resetEEMWE() {
            _eemwe.setValue(false);
            decode(); // TODO: perform only relevant checks.
        }

        public void resetEEWE() {
            _eewe.setValue(false);
            decode(); // TODO: perform only relevant checks.
        }
    }

    protected class EEPROMTicker implements Simulator.Event {

        public void fire() {

            if (devicePrinter != null) {
    //            devicePrinter.println("Tick : " + writeCount);
            }

            int address = read16(EEARH_reg, EEARL_reg);
            if (writeCount > 0) {
                // if EEWE has been written to 1 within
                // 4 clock cycles, write data

                // after 4 cycles, clear this bit

                if (writeEnableWritten) {
                    // TODO: disallow EEPROM access during Flash write
                    if (devicePrinter != null)
             //           devicePrinter.println("EEPROM: " + EEDR_reg.read() + " written to " + address);
                    EEPROM_data[address] = EEDR_reg.read();
                    // EEPROM write takes 8.5ms
                    mainClock.insertEvent(writeFinishedEvent, (long)(mainClock.getHZ() * 0.0085));
                    // CPU halts for 2 cycles
                    simulator.delay(2);
                }

            }
            // read not allowed while write is in progress
            if (readEnableWritten && !writeEnable) {
                // read
                if (devicePrinter != null)
          //          devicePrinter.println("EEPROM: " + EEPROM_data[address] + " read from " + address);
                EEDR_reg.write(EEPROM_data[address]);
                // reset EERE
                EECR_reg.resetEERE();
                // CPU halts for 4 cycles
                simulator.delay(4);
            }
            if (writeCount > 0) {
                writeCount--;
                mainClock.insertEvent(ticker, 1);
            }

            if (writeCount == 0) {
                // clear EEMWE
      //          if (devicePrinter != null) devicePrinter.println("EEPROM: write count hit 0, clearing EEMWE");
                writeCount--;
                EECR_reg.resetEEMWE();
            }
            writeEnableWritten = false;
            readEnableWritten = false;
        }
    }

    protected class EEPROMWriteFinishedEvent implements Simulator.Event {

        public void fire() {
    //        if (devicePrinter != null) devicePrinter.println("EEPROM: write finished, clearing EEWE");
            EECR_reg.resetEEWE();
        }
    }
}
