/**
 * 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.arch.avr.AVRProperties;
import avrora.sim.*;
import avrora.sim.clock.ClockDomain;
import avrora.sim.state.BooleanView;
import cck.util.Arithmetic;

/**
 * The <code>ATMegaFamily</code> class encapsulates much of the common functionality among the
 * ATMega family microcontrollers from Atmel.
 *
 * @author Ben L. Titzer
 */
public abstract class ATMegaFamily extends AtmelMicrocontroller {

    public static class FlagBit implements InterruptTable.Notification {
        final AtmelInterpreter interpreter;
        final boolean autoclear;
        final int inum;
        boolean val;

        public FlagBit(AtmelInterpreter i, boolean auto, int in) {
            interpreter = i;
            autoclear = auto;
            inum = in;
            interpreter.getInterruptTable().registerInternalNotification(this, inum);
        }

        public void flag() {
            val = true;
            interpreter.setPosted(inum, true);
        }

        public void unflag() {
            val = false;
            interpreter.setPosted(inum, false);
        }

        public boolean get() {
            return val;
        }

        @Override
        public void force(int inum) {
            val = true;
            interpreter.setPosted(inum, true);
        }

        @Override
        public void invoke(int inum) {
            if (autoclear) {
                val = false;
                interpreter.setPosted(inum, false);
            }
        }
    }

    // TODO: migrate flag register to use InterruptFlag
    public static class FlagRegister extends RWRegister {

        class Notification implements InterruptTable.Notification {
            final int bit;

            Notification(int bit) {
                this.bit = bit;
            }

            @Override
            public void force(int inum) {
                value = Arithmetic.setBit(value, bit, true);
            }

            @Override
            public void invoke(int inum) {
                value = Arithmetic.setBit(value, bit, false);
                interpreter.setPosted(inum, false);
            }
        }

        /**
         * The <code>mapping</code> array maps a bit number (0-7) to an interrupt number (0-35). This is used
         * for calculating the posted interrupts.
         */
        protected final int[] mapping;
        protected final AtmelInterpreter interpreter;

        public FlagRegister(AtmelInterpreter interp, int[] map) {
            mapping = map;
            interpreter = interp;
            InterruptTable it = interpreter.getInterruptTable();
            for (int cntr = 0; cntr < 8; cntr++) {
                if (mapping[cntr] > 0) it.registerInternalNotification(new Notification(cntr), mapping[cntr]);
            }
        }

        @Override
        public void write(byte val) {
            value = (byte) (value & ~val);
            for (int cntr = 0; cntr < 8; cntr++) {
                // do nothing for zero bits
                if (!Arithmetic.getBit(val, cntr)) continue;
                setPosted(cntr, false);
            }
        }

        private void setPosted(int inum, boolean p) {
            if (mapping[inum] > 0) interpreter.setPosted(mapping[inum], p);
        }

        public void flagBit(int bit) {
            value = Arithmetic.setBit(value, bit);
            setPosted(bit, true);
        }

        public void unflagBit(int bit) {
            value = Arithmetic.clearBit(value, bit);
            setPosted(bit, false);
        }

    }

    public static class MaskRegister extends RWRegister {

        /**
         * The <code>mapping</code> array maps a bit number (0-7) to an interrupt number (0-35). This is used
         * for calculating the posted interrupts.
         */
        protected final int[] mapping;
        protected final AtmelInterpreter interpreter;

        public MaskRegister(AtmelInterpreter interp, int[] map) {
            mapping = map;
            interpreter = interp;
        }

        @Override
        public void write(byte val) {
            value = val;
            for (int cntr = 0; cntr < 8; cntr++) {
                setEnabled(cntr, Arithmetic.getBit(val, cntr));
            }
        }

        void setEnabled(int cntr, boolean e) {
            if (mapping[cntr] > 0) interpreter.setEnabled(mapping[cntr], e);
        }

    }

    /**
     * The <code>DirectionRegister</code> class implements an active register that sets the output
     * direction of the general purpose IO pins which are present on the ATMega series.
     */
    public static class DirectionRegister extends RWRegister {

        protected ATMegaFamily.Pin[] pins;

        protected DirectionRegister(ATMegaFamily.Pin[] p) {
            pins = p;
        }

        @Override
        public void write(byte val) {
            for (int cntr = 0; cntr < 8; cntr++)
                if (pins[cntr] != null) pins[cntr].setOutputDir(Arithmetic.getBit(val, cntr));
            value = val;
        }

    }

    /**
     * The <code>PortRegister</code> class implements an active register that acts as the
     * write register (output register) for the general purpose IO pins.
     */
    public static class PortRegister extends RWRegister {
        protected ATMegaFamily.Pin[] pins;

        protected PortRegister(ATMegaFamily.Pin[] p) {
            pins = p;
        }

        @Override
        public void write(byte val) {
            for (int cntr = 0; cntr < 8; cntr++)
                if (pins[cntr] != null) pins[cntr].write(Arithmetic.getBit(val, cntr));
            value = val;
        }

    }

    /**
     * The <code>PinRegister</code> class implements an active register that acts as the
     * read register (input register) for the general purpose IO pins.
     */
    public static class PinRegister implements ActiveRegister {
        protected ATMegaFamily.Pin[] pins;

        protected PinRegister(ATMegaFamily.Pin[] p) {
            pins = p;
        }

        @Override
        public byte read() {
            int value = 0;
            for (int i = 0; i<8; i++) if (pins[i] != null) value |= pins[i].read() ? 1 << i : 0;
       /*     value |= pins[0].read() ? 1 << 0 : 0;
            value |= pins[1].read() ? 1 << 1 : 0;
            value |= pins[2].read() ? 1 << 2 : 0;
            value |= pins[3].read() ? 1 << 3 : 0;
            value |= pins[4].read() ? 1 << 4 : 0;
            value |= pins[5].read() ? 1 << 5 : 0;
            value |= pins[6].read() ? 1 << 6 : 0;
            value |= pins[7].read() ? 1 << 7 : 0;
            */
            return (byte) value;
        }

        @Override
        public void write(byte val) {
            // ignore writes.
        }

    }

    protected ATMegaFamily(ClockDomain cd, AVRProperties p, FiniteStateMachine fsm) {
        super(cd, p, fsm);
    }

    protected static final int[] periods0 = {0, 1, 8, 32, 64, 128, 256, 1024};

    /**
     * <code>Timer0</code> is the default 8-bit timer on the ATMega128.
     */
    protected class Timer0 extends Timer8Bit {

        protected Timer0() {
            super(ATMegaFamily.this, 0, 0, 1, 0, 1, 0, periods0);
            installIOReg("ASSR", new ASSRRegister());
        }

        // See pg. 104 of the ATmega128 doc
        protected class ASSRRegister extends RWRegister {
            static final int AS0 = 3;
            static final int TCN0UB = 2;
            static final int OCR0UB = 1;
            static final int TCR0UB = 0;

            @Override
            public void write(byte val) {
                super.write((byte) (0xf & val));
                decode(val);
            }

            protected void decode(byte val) {
                // TODO: if there is a change, remove ticker and requeue?
                timerClock = Arithmetic.getBit(val, AS0) ? externalClock : mainClock;
            }


        }


    }

    // CS values 6 and 7 select external clock source and are not supported. Results in an ArrayOutOfBound exception
    protected static final int[] periods0A ={0, 1, 8, 64, 256, 1024};
    /**
     * <code>Timer0A</code> is the default 8-bit timer on the ATMega128RFA1.
     */
    protected class Timer0A extends Timer8Bit {

        protected Timer0A() {
            super(ATMegaFamily.this, 1, 0, 1, 0, 1, 0, periods0A);
        }
    }

    // CS values 6 and 7 select external clock source and are not supported. Results in an ArrayOutOfBound exception
    protected static final int[] periods2 = {0, 1, 8, 64, 256, 1024};

    /**
     * <code>Timer2</code> is an additional 8-bit timer on the ATMega128. It is not available in
     * ATMega103 compatibility mode.
     */
    protected class Timer2 extends Timer8Bit {
        protected Timer2() {
            super(ATMegaFamily.this, 0, 2, 7, 6, 7, 6, periods2);
        }
    }
 
    protected static final int[] periods2A = {0, 1, 8, 32, 64, 128, 256, 1024};
    /**
     * <code>Timer2A</code> is the Timer2 implementation on the ATMega1284/ATMega128rfa1.
     * Bit 5 (AS2) of ASSR controls counting from an external crystal.
     * Bit 6 (EXCLK) allows an external clock on TOSC1
     * The same externalClock is used for either case.
     */
    protected class Timer2A extends Timer8Bit {
        protected Timer2A() {
            super(ATMegaFamily.this, 1, 2, 1, 0, 1, 0, periods2A);//TODO:OCF2B on pin 2
            installIOReg("ASSR", new ASSRRegister());
        }

        protected class ASSRRegister extends RWRegister {
            static final int TCR2BUB = 0;
            static final int TCR2AUB = 1;
            static final int OCR2BUB = 2;
            static final int OCR2AUB = 3;
            static final int TCN2B   = 4;
            static final int AS2     = 5;
            static final int EXCLK   = 6;           

            @Override
            public void write(byte val) {
                super.write((byte) (0x60 & val));
                decode(val);
            }

            protected void decode(byte val) {
                // TODO: if there is a change, remove ticker and requeue?
                if (Arithmetic.getBit(val, EXCLK)) timerClock = externalClock; else 
                timerClock = Arithmetic.getBit(val, AS2) ? externalClock : mainClock;
            }

        }
    }


    protected static final int[] periods1 = new int[]{0, 1, 8, 64, 256, 1024};

    /**
     * <code>Timer1</code> is a 16-bit timer available on the ATMega128.
     */
    protected class Timer1 extends Timer16Bit {

        @Override
        protected void initValues() {
            // bit numbers
            OCIEnA = 4;
            OCIEnB = 3;
            OCIEnC = 0;// on ETIMSK
            TOIEn = 2;
            TOVn = 2;
            OCFnA = 4;
            OCFnB = 3;
            OCFnC = 0;// on ETIFR
            ICFn = 5;

            periods = periods1;
          
            // all bits are on TIFR/TIMSK
            xTIFR_reg = TIFR_reg;
            xTIMSK_reg = TIMSK_reg;
            // except for OCIE1C and OCF1C
            cTIFR_reg = ETIFR_reg;
            cTIMSK_reg = ETIMSK_reg;
        }

        protected Timer1(int compareUnits) {
            super(1, compareUnits, ATMegaFamily.this);
        }

    }

    /**
     * <code>Timer1A</code> is a 16-bit timer available on the ATMega1284p/128RFA1.
     */
    protected class Timer1A extends Timer16Bit {

        @Override
        protected void initValues() {
            // bit numbers
            OCIEnA = 1;
            OCIEnB = 2;
            OCIEnC = 3;
            TOIEn = 0;
            TOVn = 0;
            OCFnA = 1;
            OCFnB = 2;
            OCFnC = 3;
            ICFn = 5;

            periods = periods1;
          
            // all bits are on TIFR/TIMSK
            xTIFR_reg = MTIFR1_reg;
            xTIMSK_reg = MTIMSK1_reg;
            // even  OCIE1C and OCF1C
            cTIFR_reg = MTIFR1_reg;
            cTIMSK_reg = MTIMSK1_reg;
        }

        protected Timer1A(int compareUnits) {
            super(1, compareUnits, ATMegaFamily.this);
        }

    }


    protected static final int[] periods3 = {0, 1, 8, 64, 256, 1024};

    /**
     * <code>Timer3</code> is an additional 16-bit timer available on the ATMega128, but not in ATMega103
     * compatability mode.
     */
    protected class Timer3 extends Timer16Bit {

        @Override
        protected void initValues() {
            // bit numbers
            OCIEnA = 4; // all OCIEn on ETIMSK
            OCIEnB = 3;
            OCIEnC = 1;
            TOIEn = 2;
            TOVn = 2;
            OCFnA = 4; // on OFCn on ETIFR
            OCFnB = 3;
            OCFnC = 1;
            ICFn = 5;

            periods = periods3;
          
            // all bits are on ETIFR/ETIMSK
            xTIFR_reg = ETIFR_reg;
            xTIMSK_reg = ETIMSK_reg;
            cTIFR_reg = ETIFR_reg;
            cTIMSK_reg = ETIMSK_reg;
        }

        protected Timer3(int compareUnits) {
            super(3, compareUnits, ATMegaFamily.this);
        }

    }
    
    /**
     * <code>Timer3A</code> is an additional 16-bit timer available on the ATMega128RFA1
     */
    protected class Timer3A extends Timer16Bit {

        @Override
        protected void initValues() {
            // bit numbers
            OCIEnA = 1;
            OCIEnB = 2;
            OCIEnC = 3;
            TOIEn = 0;
            TOVn = 0;
            OCFnA = 1;
            OCFnB = 2;
            OCFnC = 3;
            ICFn = 5;

            periods = periods3;
          
            // all bits are on TIFR3/TIMSK3
            xTIFR_reg = MTIFR3_reg;
            xTIMSK_reg = MTIMSK3_reg;
            cTIFR_reg = MTIFR3_reg;
            cTIMSK_reg = MTIMSK3_reg;
        }

        protected Timer3A(int compareUnits) {
            super(3, compareUnits, ATMegaFamily.this);
        }

    }


    /**
     * The <code>buildPort()</code> method builds the IO registers corresponding to a general purpose IO port.
     * These ports are named A-G, and each consist of a PORT register (for writing), a PIN register (for reading),
     * and a direction register for setting whether each pin in the port is input or output. This method
     * is a utility to build these registers for each port given the last character of the name (e.g. 'A' in
     * PORTA).
     *
     * @param p the last character of the port name
     */
    protected void buildPort(char p) {
        Pin[] portPins = new Pin[8];
        int cntr=0;
        try {
            for (cntr = 0; cntr < 8; cntr++)
                portPins[cntr] = (Pin) getPin("P" + p + cntr);
        } catch (Exception e) {
            System.err.println("Avrora: Pins " + cntr + " and above are not defined for PORT" + p);
        }
        installIOReg("PORT" + p, new PortRegister(portPins));
        installIOReg("DDR" + p, new DirectionRegister(portPins));
        installIOReg("PIN" + p, new PinRegister(portPins));
    }

    /**
     * The <code>buildInterruptRange()</code> method creates the IO registers and <code>MaskableInterrupt</code>
     * instances corresponding to a complete range of interrupts.
     *
     * @param increasing a flag indicating that the vector numbers increase with bit number of the IO register
     * @param maskRegNum the IO register number of the mask register
     * @param flagRegNum the IO register number of the flag register
     * @param baseVect   the beginning vector of this range of interrupts
     * @param numVects   the number of vectors in this range
     * @return a flag register that corresponds to the interrupt range
     */
    protected FlagRegister buildInterruptRange(boolean increasing, String maskRegNum, String flagRegNum, int baseVect, int numVects) {
        int[] mapping = new int[8];
        if (increasing) {
            for (int cntr = 0; cntr < 8; cntr++) mapping[cntr] = baseVect + cntr;
        } else {
            for (int cntr = 0; cntr < 8; cntr++) mapping[cntr] = baseVect - cntr;
        }

        FlagRegister fr = new FlagRegister(interpreter, mapping);
        MaskRegister mr = new MaskRegister(interpreter, mapping);
        installIOReg(maskRegNum, mr);
        installIOReg(flagRegNum, fr);
        return fr;
    }


    protected FlagRegister EIFR_reg;

    protected FlagRegister TIFR_reg,TIFR1_reg,TIFR3_reg;
    protected MaskRegister TIMSK_reg,TIMSK1_reg,TIMSK3_reg;

    protected FlagRegister ETIFR_reg,MTIFR1_reg,MTIFR3_reg;
    protected MaskRegister ETIMSK_reg,MTIMSK1_reg,MTIMSK3_reg;

    /**
     * The getEIFR_reg() method is used to access the external interrupt flag register.
     *
     * @return the <code>ActiveRegister</code> object corresponding to the EIFR IO register
     */
    public FlagRegister getEIFR_reg() {
        return EIFR_reg;
    }

    public static class InterruptFlag implements InterruptTable.Notification {
        final AtmelInterpreter interpreter;
        final boolean autoclear;
        final int inum;
        final BooleanView view;

        public InterruptFlag(AtmelInterpreter i, boolean auto, int in, BooleanView b) {
            interpreter = i;
            autoclear = auto;
            inum = in;
            interpreter.getInterruptTable().registerInternalNotification(this, inum);
            view = b;
        }

        public void flag(boolean flag) {
            view.setValue(flag);
            interpreter.setPosted(inum, flag);
        }

        public boolean get() {
            return view.getValue();
        }

        @Override
        public void force(int inum) {
            view.setValue(true);
            interpreter.setPosted(inum, true);
        }

        public void sync() {
            interpreter.setPosted(inum, view.getValue());
        }

        @Override
        public void invoke(int inum) {
            if (autoclear) {
                view.setValue(false);
                interpreter.setPosted(inum, false);
            }
        }
    }
}
