/**
 * 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 cck.text.StringUtil;
import cck.util.Arithmetic;
import cck.util.Util;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.*;

/**
 * The <code>RegisterLayout</code> class stores information about the IO register addresses for a particular
 * microcontroller model. It maps the names of the IO registers to their addresses and also contains information
 * about which fields are stored in which registers. When a microcontroller is instantiated, this class is used
 * to create an instance of <code>RegisterSet</code> that corresponds to the layout of the registers on
 * this device.
 *
 * @author Ben L. Titzer
 */
public class RegisterLayout {

    protected static class RegisterInfo {
        final String name;
        final int ior_num;
        SubField[] subfields;

        RegisterInfo(String n, int ior) {
            name = n;
            ior_num = ior;
        }
    }

    protected static class Field {
        final String name;
        int length;
        SubField[] subfields;

        Field(String n) {
            name = n;
        }

        void add(SubField sf) {
            if ( subfields == null ) {
                subfields = new SubField[1];
            } else {
                SubField[] nsf = new SubField[subfields.length+1];
                System.arraycopy(subfields, 0, nsf, 0, subfields.length);
                subfields = nsf;
            }
            subfields[subfields.length - 1] = sf;
            int highbit = sf.field_low_bit + sf.length;
            if ( highbit > length ) length = highbit;
        }
    }

    public static final Field UNUSED = new Field("UNUSED");
    public static final Field RESERVED = new Field("RESERVED");

    protected static class SubField {
        final Field field;       // the name of the field
        final int ior;            // the IO register containing this subfield
        int length;
        int ior_low_bit;    // low bit in the IO register
        int field_low_bit;  // offset (low bit) in the field
        int mask;           // low bit in the IO register
        boolean commit;     // true if a write to this subfield should commit

        SubField(Field f, int ior) {
            this.field = f;
            this.ior = ior;
        }
    }

    /**
     * The <code>ioreg_size</code> field stores the number of IO registers on this microcontroller.
     */
    public final int ioreg_size;

    /**
     * The <code>ioreg_length</code> field stores the length of each register in bits.
     */
    public final int ioreg_length;

    /**
     * The <code>ioregAssignments</code> field stores a reference to a hashmap from IO register names to their
     * addresses.
     */
    protected final HashMap<String, RegisterInfo> ioregAssignments;

    protected final RegisterInfo[] info;

    /**
     * The <code>fields</code> field stores a reference to a hashmap that maps from a field name to a representation
     * of the field.
     */
    protected final HashMap<String, Field> fields;

    /**
     * The constructor for the <code>RegisterLayout</code> class creates a new register layout with the specified
     * size.
     * @param is the number of registers in this register layout
     */
    public RegisterLayout(int is, int rlength) {
        ioreg_size = is;
        ioregAssignments = new HashMap<String, RegisterInfo>();
        fields = new HashMap<String, Field>();
        info = new RegisterInfo[is];
        ioreg_length = rlength;
    }

    /**
     * The <code>addIOReg()</code> method adds a new IO register with the specified name and address to this
     * register layout.
     * @param n the name of the IO register
     * @param ior_num the address of the IO register
     */
    public void addIOReg(String n, int ior_num) {
        if ( ior_num >= ioreg_size )
            throw new Util.Error("Layout Error", "invalid register address "+ior_num+" for register "+ StringUtil.quote(n));
        RegisterInfo i = new RegisterInfo(n, ior_num);
        info[ior_num] = i;
        ioregAssignments.put(n, i);
    }

    /**
     * The <code>addIOReg()</code> method adds a new IO register with the specified name and address to this
     * register layout. This variant of the method also accepts a format string describing which fields are
     * in this IO register. The format description is used to automatically generate an instance of the IO
     * register that puts the right bits of the right fields in the right places when the register is
     * written to.
     * @param n the name of the IO register
     * @param ior_num the address of the IO register
     * @param format the format of the IO register as a string describing which fields and which bits of
     * which fields are present in this IO register
     */
    public void addIOReg(String n, int ior_num, String format) {
        if ( ior_num >= ioreg_size )
            throw new Util.Error("Layout Error", "invalid register address "+ior_num+" for register "+ StringUtil.quote(n));
        RegisterInfo i = new RegisterInfo(n, ior_num);
        i.subfields = parseSubFields(n, ior_num, format);
        info[ior_num] = i;
        ioregAssignments.put(n, i);
    }

    /**
     * The <code>getIOReg()</code> method retrieves the IO register number for the given IO
     * Register name for this microcontroller.
     * @param n the name of the IO register such as "TCNT0"
     * @return an integer representing the IO register number if it exists
     * @throws NoSuchElementException if the specified IO register name does not have an assignment
     */
    public int getIOReg(String n) {
        RegisterInfo i = (RegisterInfo)ioregAssignments.get(n);
        if ( i == null )
            throw new NoSuchElementException(StringUtil.quote(n)+" IO register not found");
        return i.ior_num;
    }

    /**
     * The <code>hasIOReg()</code> method simply checks whether this register layout has a register with
     * the specified name.
     * @param n the name of the IO register
     * @return true if this layout has a register with the specified name; false otherwise
     */
    public boolean hasIOReg(String n) {
        return ioregAssignments.containsKey(n);
    }

    /**
     * The <code>instantiate()</code> method creates a new register set that contains the actual register
     * implementations that can be used in simulation.
     * @return a new register set to be used in simulation
     */
    public RegisterSet instantiate() {
        return new RegisterSet(this);
    }

    public String getRegisterName(int ior) {
        RegisterInfo registerInfo = info[ior];
        return registerInfo != null ? registerInfo.name : "";
    }

    private SubField[] parseSubFields(String name, int ior, String desc) {
        int totalbits = 0;
        int count = 0;
        SubField[] sfs = new SubField[8];
        StringCharacterIterator i = new StringCharacterIterator(desc);
        int ior_hbit = 7;
        while ( ior_hbit >= 0 && i.current() != CharacterIterator.DONE ) {
            if ( i.current() == '.') {
                ior_hbit = readUnusedField(i, sfs, count, ior_hbit);
                totalbits += sfs[count].length;
            } else if ( i.current() == 'x') {
                ior_hbit = readReservedField(i, sfs, count, ior_hbit);
                totalbits += sfs[count].length;
            } else {
                ior_hbit = readNamedField(i, ior, sfs, count, ior_hbit);
                totalbits += sfs[count].length;
            }
            count++;
            StringUtil.peekAndEat(i, ',');
            StringUtil.skipWhiteSpace(i);
        }

        // check that there are exactly 8 bits
        if ( totalbits != ioreg_length ) {
            throw new Util.Error("Layout Error", "expected "+ioreg_length+" bits, found: "+totalbits+" in "+StringUtil.quote(name));
        }

        // resize the array to be smaller
        SubField[] subFields = new SubField[count];
        System.arraycopy(sfs, 0, subFields, 0, count);
        // calculate the commit points (i.e. last write to the field)
        HashSet<Field> fs = new HashSet<Field>();
        for ( int cntr = subFields.length - 1; cntr >= 0; cntr-- ) {
            SubField subField = subFields[cntr];
            if ( !fs.contains(subField.field) ) subField.commit = true;
            fs.add(subField.field);
        }
        return subFields;
    }

    private int readNamedField(StringCharacterIterator i, int ior, SubField[] sfs, int count, int ior_hbit) {
        // named field is specified
        String fid = StringUtil.readIdentifier(i);
        Field field = getField(fid);
        SubField sf = new SubField(field, ior);
        field.add(sf);
        sfs[count] = sf;
        if ( StringUtil.peekAndEat(i, '[') ) {
            ior_hbit = readBitRange(i, ior_hbit, sf);
        } else {
            ior_hbit = readBit(ior_hbit, sf);
        }
        return ior_hbit;
    }

    private int readReservedField(StringCharacterIterator i, SubField[] sfs, int count, int ior_hbit) {
        // reserved field is specified
        SubField sf = new SubField(RESERVED, -1);
        sfs[count] = sf;
        ior_hbit = eat(ior_hbit, i, sf, 'x');
        return ior_hbit;
    }

    private int readUnusedField(StringCharacterIterator i, SubField[] sfs, int count, int ior_hbit) {
        // unused field is specified
        SubField sf = new SubField(UNUSED, -1);
        sfs[count] = sf;
        ior_hbit = eat(ior_hbit, i, sf, '.');
        return ior_hbit;
    }

    private int readBit(int ior_hbit, SubField sf) {
        // no bit range is specified; assume only one bit
        sf.ior_low_bit = ior_hbit;
        sf.field_low_bit = 0;
        sf.mask = 0x1;
        sf.length = 1;
        ior_hbit--;
        return ior_hbit;
    }

    private int readBitRange(StringCharacterIterator i, int ior_hbit, SubField sf) {
        // a bit range is specified; create a subfield
        int fhbit = StringUtil.readDecimalValue(i, 1);
        int flbit = fhbit;
        if ( StringUtil.peekAndEat(i, ':')) {
            flbit =  StringUtil.readDecimalValue(i, 1);
        }
        int length = fhbit - flbit + 1;
        sf.ior_low_bit = ior_hbit - length + 1;
        sf.field_low_bit = flbit;
        sf.mask = 0xff >> (8 - length);
        sf.length = length;
        StringUtil.peekAndEat(i, ']');
        ior_hbit -= length;
        return ior_hbit;
    }

    private int eat(int ior_hbit, StringCharacterIterator i, SubField sf, char c) {
        int hbit = ior_hbit;
        while ( i.current() == c ) {
            sf.length++;
            i.next();
        }
        sf.ior_low_bit = hbit - sf.length + 1;
        sf.mask = Arithmetic.getBitMask(sf.length);
        ior_hbit -= sf.length;
        return ior_hbit;
    }

    private Field getField(String name) {
        Field f = (Field)fields.get(name);
        if ( f == null ) {
            f = new Field(name);
            fields.put(name, f);
        }
        return f;
    }
}
