/**
 * Copyright (c) 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.
 *
 * Creation date: Dec 5, 2005
 */

package avrora.syntax.raw;

import avrora.Main;
import avrora.arch.AbstractArchitecture;
import avrora.core.Program;
import avrora.core.ProgramReader;
import cck.text.StringUtil;
import cck.util.Arithmetic;
import cck.util.Util;
import java.io.BufferedReader;
import java.io.FileReader;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.*;

/**
 * @author Ben L. Titzer
 */
public class RAWReader extends ProgramReader {

    protected class Record {

        protected final int addr;
        protected boolean code;
        protected List<Byte> bytes;
        protected List<String> strings;

        protected Record(int addr) {
            this.addr = addr;
            bytes = new ArrayList<Byte>(4);
            strings = new ArrayList<String>(1);
        }
    }

    boolean inCode;

    public RAWReader() {
        super("The \"raw\" program format reader reads programs that consist of small records of " + "bytes and instructions.");
    }

    public Program read(String[] args) throws Exception {
        if (args.length == 0) Util.userError("no input files");
        if (args.length != 1) Util.userError("input type \"raw\" accepts only one file at a time.");
        AbstractArchitecture arch = getArchitecture();
        String fname = args[0];
        List<Record> records = parseFile(fname);
        Program p = createProgram(arch, records);
        loadProgram(p, records);
        return p;
    }

    private List<Record> parseFile(String fname) throws Exception {
        Main.checkFileExists(fname);
        BufferedReader reader = new BufferedReader(new FileReader(fname));
        List<Record> records = new LinkedList<Record>();
        int cntr = 1;
        while (true) {
            String line = reader.readLine();
            if (line == null) break;
            Record r = parse(cntr++, line);
            if (r != null) records.add(r);
        }
        reader.close();
        return records;
    }

    private Program createProgram(AbstractArchitecture arch, List<Record> records) {
        boolean init = false;
        int min = 0;
        int max = 0;
        for (Record r : records) {
            if (init) {
                min = Arithmetic.min(min, r.addr);
                max = Arithmetic.max(max, r.addr + r.bytes.size());
            } else {
                init = true;
                min = r.addr;
                max = r.addr + r.bytes.size();
            }
        }
        return new Program(arch, min, max);
    }

    private void loadProgram(Program p, List<Record> records) {
        for (Record r : records) {
            loadBytes(r, p);
            loadInstr(r, p);
        }
    }

    private void loadBytes(Record r, Program p) {
        int pos = r.addr;
        for (Byte by : r.bytes) {
            p.writeProgramByte(by.byteValue(), pos++);
        }
    }

    private void loadInstr(Record r, Program p) {
        if (r.code) {
            for (int pos = r.addr; pos < r.addr + r.bytes.size(); pos += 2)
                p.disassembleInstr(pos);
        }
    }

    // parses lines such as: 0x0000: 01 02 "instr" ; comment
    protected Record parse(int lineno, String line) throws Exception {
        CharacterIterator i = new StringCharacterIterator(line);
        StringUtil.skipWhiteSpace(i);
        char ch = i.current();

        if (ch == CharacterIterator.DONE) return null; // empty line
        if (ch == ';') return null; // line consists of comment only
        if (ch == '.') return readDirective(i);
        else return readRecord(ch, lineno, i);
    }

    private Record readRecord(char ch, int lineno, CharacterIterator i) throws Exception {
        if (!StringUtil.isHexDigit(ch)) Util.userError("syntax error @ " + lineno + ':' + i.getIndex());

        Record record = new Record(readAddress(i, ch));
        record.code = inCode;

        StringUtil.expectChar(i, ':'); // expect a colon

        while (true) { // read in the bytes and strings one by one
            StringUtil.skipWhiteSpace(i);
            ch = i.current();
            if (StringUtil.isHexDigit(ch)) readByte(record, i);
            else if (ch == '"') readString(record, i);
            else if (ch == ';') break;
            else if (ch == CharacterIterator.DONE) break;
            else Util.userError("syntax error at " + i.getIndex());
        }
        return record;
    }

    private Record readDirective(CharacterIterator i) {
        // line consists of a directive
        i.next();
        String dir = StringUtil.readIdentifier(i);
        if ("code".equals(dir)) inCode = true;
        else if ("data".equals(dir)) inCode = false;
        return null;
    }

    private int readAddress(CharacterIterator i, char ch) {
        if (ch == '0') {
            i.next();
            StringUtil.peekAndEat(i, 'x');
        }
        return StringUtil.readHexValue(i, 8);
    }

    private void readByte(Record record, CharacterIterator i) {
        int readByte = StringUtil.readHexValue(i, 2);
        record.bytes.add(new Byte((byte)readByte));
        if (StringUtil.isHexDigit(i.current())) Util.userError("constant too long");
    }

    private void readString(Record record, CharacterIterator i) {
        char ch;
        StringBuffer buf = new StringBuffer();
        while ((ch = i.next()) != CharacterIterator.DONE) {
            if (ch == '"') {
                i.next();
                break;
            }
            buf.append(ch);
        }
        record.strings.add(buf.toString());
    }

}
