"""Core pragmas

    Provides the core assembler directives."""

# Copyright 2002-2014 Michael C. Martin and additional contributors.
# You may use, modify, and distribute this file under the MIT
# license: See README for details.

import Ophis.CmdLine
import Ophis.IR as IR
import Ophis.Frontend as FE
import Ophis.Errors as Err
import math
import os.path

basecharmap = "".join([chr(x) for x in range(256)])
currentcharmap = basecharmap


def reset():
    global currentcharmap, basecharmap
    FE.loadedfiles = {}
    currentcharmap = basecharmap


def pragmaOutfile(ppt, line, result):
    "Sets the output file if it hasn't already been set"
    filename = line.expect("STRING").value
    line.expect("EOL")
    if type(filename) == str and Ophis.CmdLine.outfile is None:
        Ophis.CmdLine.outfile = filename


def pragmaListfile(ppt, line, result):
    "Sets the listing file if it hasn't already been set"
    filename = line.expect("STRING").value
    line.expect("EOL")
    if type(filename) == str and Ophis.CmdLine.listfile is None:
        Ophis.CmdLine.listfile = filename


def pragmaInclude(ppt, line, result):
    "Includes a source file"
    filename = line.expect("STRING").value
    line.expect("EOL")
    if type(filename) == str:
        result.append(FE.parse_file(ppt, filename))


def pragmaRequire(ppt, line, result):
    "Includes a source file at most one time"
    filename = line.expect("STRING").value
    line.expect("EOL")
    if type(filename) == str:
        result.append(FE.parse_file(ppt, filename, True))


def pragmaIncbin(ppt, line, result):
    "Includes a binary file"
    filename = line.expect("STRING").value
    offset = IR.ConstantExpr(0)
    size = None
    if str(line.lookahead(0)) == ",":
        line.pop()
        offset = FE.parse_expr(line)
        if str(line.lookahead(0)) == ",":
            line.pop()
            size = FE.parse_expr(line)
    line.expect("EOL")
    if type(filename) == str:
        try:
            f = open(os.path.join(FE.context_directory, filename), "rb")
            if offset.hardcoded and (size is None or size.hardcoded):
                # We know how big it will be, we can just use the values.
                # First check to make sure they're sane
                if offset.value() < 0:
                    Err.log("Offset may not be negative")
                    f.close()
                    return
                f.seek(0, 2)  # Seek to end of file
                if offset.value() > f.tell():
                    Err.log("Offset runs past end of file")
                    f.close()
                    return
                if size is not None:
                    if size.value() < 0:
                        Err.log("Length may not be negative")
                        f.close()
                        return
                    if offset.value() + size.value() > f.tell():
                        Err.log(".incbin length too long")
                        f.close()
                        return
                if size is None:
                    size = IR.ConstantExpr(-1)
                f.seek(offset.value())
                bytes = f.read(size.value())
                bytes = [IR.ConstantExpr(x) for x in bytes]
                result.append(IR.Node(ppt, "Byte", *bytes))
            else:
                # offset or length could change based on label placement.
                # This seems like an unbelievably bad idea, but since we
                # don't have constant prop it will happen for any symbolic
                # alias. Don't use symbolic aliases when extracting tiny
                # pieces out of humongous files, I guess.
                bytes = f.read()
                bytes = [IR.ConstantExpr(x) for x in bytes]
                if size is None:
                    size = IR.SequenceExpr([IR.ConstantExpr(len(bytes)),
                                            "-",
                                            offset])
                result.append(IR.Node(ppt, "ByteRange", offset, size, *bytes))
            f.close()
        except IOError:
            Err.log("Could not read " + filename)
            return


def pragmaCharmap(ppt, line, result):
    "Modify the character map."
    global currentcharmap, basecharmap
    if str(line.lookahead(0)) == "EOL":
        currentcharmap = basecharmap
    else:
        bytes = readData(line)
        try:
            base = bytes[0].data
            newsubstr = "".join([chr(x.data) for x in bytes[1:]])
            currentcharmap = currentcharmap[:base] + newsubstr + \
                             currentcharmap[base + len(newsubstr):]
            if len(currentcharmap) != 256 or base < 0 or base > 255:
                Err.log("Charmap replacement out of range")
                currentcharmap = currentcharmap[:256]
        except ValueError:
            Err.log("Illegal character in .charmap directive")


def pragmaCharmapbin(ppt, line, result):
    "Load a new character map from a file"
    global currentcharmap
    filename = line.expect("STRING").value
    line.expect("EOL")
    if type(filename) == str:
        try:
            f = open(os.path.join(FE.context_directory, filename), "rb")
            bytes = f.read()
            f.close()
        except IOError:
            Err.log("Could not read " + filename)
            return
        if len(bytes) == 256:
            currentcharmap = bytes
        else:
            Err.log("Character map " + filename + " not 256 bytes long")


def pragmaOrg(ppt, line, result):
    "Relocates the PC with no output"
    newPC = FE.parse_expr(line)
    line.expect("EOL")
    result.append(IR.Node(ppt, "SetPC", newPC))


def pragmaAdvance(ppt, line, result):
    "Outputs filler until reaching the target PC"
    newPC = FE.parse_expr(line)
    if str(line.lookahead(0)) == ",":
        line.pop()
        fillexpr = FE.parse_expr(line)
    else:
        fillexpr = IR.ConstantExpr(0)
    line.expect("EOL")
    result.append(IR.Node(ppt, "Advance", newPC, fillexpr))


def pragmaCheckpc(ppt, line, result):
    "Enforces that the PC has not exceeded a certain point"
    target = FE.parse_expr(line)
    line.expect("EOL")
    result.append(IR.Node(ppt, "CheckPC", target))


def pragmaAlias(ppt, line, result):
    "Assigns an arbitrary label"
    lbl = line.expect("LABEL").value
    target = FE.parse_expr(line)
    result.append(IR.Node(ppt, "Label", lbl, target))


def pragmaSpace(ppt, line, result):
    "Reserves space in a data segment for a variable"
    lbl = line.expect("LABEL").value
    size = line.expect("NUM").value
    line.expect("EOL")
    result.append(IR.Node(ppt, "Label", lbl, IR.PCExpr()))
    result.append(IR.Node(ppt, "SetPC",
                          IR.SequenceExpr([IR.PCExpr(), "+",
                                           IR.ConstantExpr(size)])))


def pragmaText(ppt, line, result):
    "Switches to a text segment"
    next = line.expect("LABEL", "EOL")
    if next.type == "LABEL":
        line.expect("EOL")
        segment = next.value
    else:
        segment = "*text-default*"
    result.append(IR.Node(ppt, "TextSegment", segment))


def pragmaData(ppt, line, result):
    "Switches to a data segment (no output allowed)"
    next = line.expect("LABEL", "EOL")
    if next.type == "LABEL":
        line.expect("EOL")
        segment = next.value
    else:
        segment = "*data-default*"
    result.append(IR.Node(ppt, "DataSegment", segment))


def pragmaCbmfloat(ppt, line, result):
    "Parses a string into a CBM BASIC format floating point number"
    data = []
    while True:
        try:
            v_str = line.expect("STRING").value
            v = float(v_str)
            if v == 0.0:
                data.extend([0,0,0,0,0])
            else:
                if v < 0.0:
                    sign = 128
                    v = -v
                else:
                    sign = 0
                expt = math.floor(math.log(v, 2))
                if expt >= -128 and expt <= 126:
                    mantissa = v / (2**expt)
                    m1 = (mantissa - 1.0) * 128 + sign
                    m2 = m1 * 256
                    m3 = m2 * 256
                    m4 = m3 * 256
                    data.extend([int(x) % 256 for x in [expt+129,m1,m2,m3,m4]])
                else:
                    Err.log("Floating point constant out of range")
        except ValueError:
            Err.log("Expected: floating point")
        next = line.expect(',', 'EOL').type
        if next == 'EOL':
            break
    bytes = [IR.ConstantExpr(x) for x in data]
    result.append(IR.Node(ppt, "Byte", *bytes))


def readData(line):
    "Read raw data from a comma-separated list"
    if line.lookahead(0).type == "STRING":
        data = [IR.ConstantExpr(ord(x))
                for x in line.expect("STRING").value.translate(currentcharmap)]
    else:
        data = [FE.parse_expr(line)]
    next = line.expect(',', 'EOL').type
    while next == ',':
        if line.lookahead(0).type == "STRING":
            data.extend([IR.ConstantExpr(ord(x))
                         for x in line.expect("STRING").value])
        else:
            data.append(FE.parse_expr(line))
        next = line.expect(',', 'EOL').type
    return data


def pragmaByte(ppt, line, result):
    "Raw data, a byte at a time"
    bytes = readData(line)
    result.append(IR.Node(ppt, "Byte", *bytes))


def pragmaWord(ppt, line, result):
    "Raw data, a word at a time, little-endian"
    words = readData(line)
    result.append(IR.Node(ppt, "Word", *words))


def pragmaDword(ppt, line, result):
    "Raw data, a double-word at a time, little-endian"
    dwords = readData(line)
    result.append(IR.Node(ppt, "Dword", *dwords))


def pragmaWordbe(ppt, line, result):
    "Raw data, a word at a time, big-endian"
    words = readData(line)
    result.append(IR.Node(ppt, "WordBE", *words))


def pragmaDwordbe(ppt, line, result):
    "Raw data, a dword at a time, big-endian"
    dwords = readData(line)
    result.append(IR.Node(ppt, "DwordBE", *dwords))


def pragmaScope(ppt, line, result):
    "Create a new lexical scoping block"
    line.expect("EOL")
    result.append(IR.Node(ppt, "ScopeBegin"))


def pragmaScend(ppt, line, result):
    "End the innermost lexical scoping block"
    line.expect("EOL")
    result.append(IR.Node(ppt, "ScopeEnd"))


def pragmaMacro(ppt, line, result):
    "Begin a macro definition"
    lbl = line.expect("LABEL").value
    line.expect("EOL")
    result.append(IR.Node(ppt, "MacroBegin", lbl))


def pragmaMacend(ppt, line, result):
    "End a macro definition"
    line.expect("EOL")
    result.append(IR.Node(ppt, "MacroEnd"))


def pragmaInvoke(ppt, line, result):
    macro = line.expect("LABEL").value
    if line.lookahead(0).type == "EOL":
        args = []
    else:
        args = readData(line)
    result.append(IR.Node(ppt, "MacroInvoke", macro, *args))
