/**
 * 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 cck.util.Arithmetic;

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

    // TODO: merge shared classes with ATMegaFamily
    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;
        }

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

        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;
            }

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

            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]);
            }
        }

        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;
        }

        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 Pin[] pins;

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

        public void write(byte val) {
            for (int bit = 0; bit < pins.length; bit++)
                if (pins[bit] != null) pins[bit].setOutputDir(Arithmetic.getBit(val, bit));
            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 Pin[] pins;

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

        public void write(byte val) {
            for (int bit = 0; bit < pins.length; bit++)
                if (pins[bit] != null) pins[bit].write(Arithmetic.getBit(val, bit));
            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 Pin[] pins;

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

        public byte read() {
            byte value = 0;
            for (int bit = 0; bit < pins.length; bit++)
                value |= pinHigh(bit) ? 1 << bit : 0;
            return value;
        }

        private boolean pinHigh(int bit) {
            return pins[bit] != null && pins[bit].read();
        }

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

    }

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

    /**
     * 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) {
        buildPort(p, 8);
    }

    protected void buildPort(char p, int pins) {
        Pin[] portPins = new Pin[8];
        for (int bit = 0; bit < pins; bit++)
            portPins[bit] = (Pin) getPin("P" + p + bit);
        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 bit = 0; bit < numVects; bit++) mapping[bit] = baseVect + bit;
        } else {
            for (int bit = 0; bit < numVects; bit++) mapping[bit] = baseVect - bit;
        }
        for (int i = numVects; i < mapping.length; i++) {
            mapping[i] = -1;
        }
        FlagRegister fr = new FlagRegister(interpreter, mapping);
        MaskRegister mr = new MaskRegister(interpreter, mapping);
        installIOReg(maskRegNum, mr);
        installIOReg(flagRegNum, fr);
        return fr;
    }


    protected FlagRegister EIFR_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;
    }

}
