/**
 * 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.syntax;

import avrora.arch.legacy.LegacyInstrProto;
import cck.parser.AbstractToken;
import cck.util.Util;

/**
 * The <code>Item</code> class represents either an assembler directive, an instruction, or a sequence of
 * initialized data with a source program.
 */
public abstract class Item {

    protected final Module module;
    protected final Module.Seg segment;
    protected final int byteAddress;

    /**
     * The <code>simplify()</code> method reduces any computable constants to values, resolves register
     * aliases, and creates instruction instances within this item, depending on exactly which type of item it
     * is.
     */
    public abstract void simplify();

    protected Item(Module.Seg seg) {
        byteAddress = seg.getCurrentAddress();
        segment = seg;
        module = seg.getModule();
    }

    public int itemSize() {
        return 0;
    }

    /**
     * The <code>NamedConstant</code> item in a source program represents a directive that assigns a
     * computable value to a name.
     */
    public static class NamedConstant extends Item {

        private final AbstractToken name;
        private final Expr value;

        NamedConstant(Module.Seg s, AbstractToken n, Expr v) {
            super(s);
            name = n;
            value = v;
        }

        public void simplify() {
            int result = value.evaluate(byteAddress, module);
            module.addVariable(name.image, result);
        }

        public String toString() {
            return ".equ " + name;
        }
    }

    /**
     * The <code>RegisterAlias</code> item in a source program represents a directive that adds an alias for a
     * register. This can appear in program, data, and eeprom segments.
     */
    public static class RegisterAlias extends Item {

        private final AbstractToken name;
        private final AbstractToken register;

        RegisterAlias(Module.Seg s, AbstractToken n, AbstractToken r) {
            super(s);
            name = n;
            register = r;
        }

        public void simplify() {
            module.addRegisterName(name.image, register);
        }

        public String toString() {
            return ".def " + name + " = " + register;
        }
    }


    /**
     * The <code>Instruction</code> item in a source program represents an instruction that must be simplified
     * and added to the program. This is generally only applicable to the program (code) section.
     */
    public static class Instruction extends Item {

        protected final String variant;
        protected final AbstractToken name;
        protected final SyntacticOperand[] operands;
        protected final LegacyInstrProto proto;

        Instruction(Module.Seg s, String v, AbstractToken n, LegacyInstrProto p, SyntacticOperand[] ops) {
            super(s);
            variant = v;
            name = n;
            operands = ops;
            proto = p;
        }

        public void simplify() {

            // TODO: define correct error for this
            if ((byteAddress & 0x1) == 0x1) throw Util.failure("misaligned instruction");

            for (int cntr = 0; cntr < operands.length; cntr++)
                operands[cntr].simplify(byteAddress + proto.getSize(), module);

            segment.writeInstr(name, byteAddress, proto.build(byteAddress >> 1, operands));
        }

        public int itemSize() {
            return proto.getSize();
        }

        public String toString() {
            return "instr: " + variant + " @ " + byteAddress;
        }
    }

    /**
     * The <code>Label</code> item represents a labelled location in the program that is given a name. This
     * can appear in program, data, or eeprom sections.
     */
    public static class Label extends Item {

        private final AbstractToken name;

        Label(Module.Seg s, AbstractToken n) {
            super(s);
            name = n;
        }

        public void simplify() {
            segment.addLabel(name.image, byteAddress, byteAddress, 1);
        }

        public int getByteAddress() {
            return byteAddress;
        }

        public String toString() {
            return "label: " + name + " in " + segment.getName() + " @ " + byteAddress;
        }
    }

    /**
     * The <code>InitializedData</code> item represents a section of programmer-declared initialized data
     * within the program. This is generally only applicable to the program (code) section.
     */
    public static class InitializedData extends Item {

        private final ExprList list;
        private final int width;
        private final int size;

        InitializedData(Module.Seg s, ExprList l, int w) {
            super(s);
            list = l;
            width = w;
            size = computeSize(l, w);
        }

        public void simplify() {
            int cursor = byteAddress;

            for (ExprList.ExprItem item = list.head; item != null; item = item.next) {
                Expr e = item.expr;
                if (e instanceof Expr.StringLiteral) {
                    cursor = writeString(e, segment, cursor);
                } else {
                    cursor = writeValue(e, module, segment, cursor);
                }
            }
        }

        private int writeValue(Expr e, Module module, Module.Seg s, int cursor) {
            int val = e.evaluate(byteAddress, module);
            for (int cntr = 0; cntr < width; cntr++) {
                s.writeDataByte(e, cursor, (byte)val);
                val = val >> 8;
                cursor++;
            }
            return cursor;
        }

        private int writeString(Expr e, Module.Seg s, int cursor) {
            String str = ((Expr.StringLiteral)e).value;
            // TODO: should this string literal be evaluated first?
            s.writeDataBytes(e, cursor, str.getBytes());
            // align the cursor
            return align(cursor, width);
        }

        private int computeSize(ExprList l, int width) {
            int count = 0;
            for (ExprList.ExprItem item = l.head; item != null; item = item.next) {
                Expr e = item.expr;
                if (e instanceof Expr.StringLiteral) {
                    // TODO: is this the right size?
                    count += align(((Expr.StringLiteral)e).value.length(), width);
                } else {
                    count += width;
                }
            }
            return count;
        }

        private int align(int cursor, int width) {
            if (width > 1) {
                int i = cursor % width;
                cursor = i == 0 ? cursor : cursor + (i - width);
            }
            return cursor;
        }

        public int itemSize() {
            return size;
        }

        public String toString() {
            return "initialized data @ " + byteAddress;
        }
    }

    /**
     * The <code>UnitializedData</code> item represents a declared section of data that is not given a value
     * (a reservation of space). This can appear in the program, data, or eeprom segments.
     */
    public static class UninitializedData extends Item {

        private final int length;

        UninitializedData(Module.Seg s, int l) {
            super(s);
            length = l;
        }

        public void simplify() {
            // nothing to build with a reserve item
        }

        public String toString() {
            return "reserve " + length + " in " + segment.getName();
        }

        public int itemSize() {
            return length;
        }
    }
}
