#!/usr/bin/env python

"""
mipsy - Basic MIPS assembler
See README.md for usage and general information.
"""

# system imports
import re
import argparse
import logging

# application imports
from mipsy.encoder import Encoder
from mipsy.util import LabelCache


class MIPSAssembler(object):
    """
    Responsible for file I/O and building the final instruction memory.
    Relies on the Encoder to build individual instruction bit strings.
    """

    def __init__(self):
        # Initialize and load command line args
        argparser = argparse.ArgumentParser(description='(Extremely) basic MIPS32 assembler.')

        argparser.add_argument('in_path')
        argparser.add_argument('-o', dest='out_path', default='out.bin')

        self.args = argparser.parse_args()

        # List of encoded instructions and their index (PC)
        self.instructions = []
        self.pc = 0

        # Label cache
        # label --> instruction index (PC)
        self.label_cache = LabelCache()

        # Instruction encoder
        self.encoder = Encoder()

    def run(self):
        # Regular expression to match input against
        # - If a match is made, update the label cache and store the instruction
        # for later parsing (if present)
        # - If no match is made, we assume the current line is an instruction
        # and attempt to parse as such.
        label_test = re.compile(r'(?P<label>[\w]+)[ ]*:[ ]*(?P<instruction>.*)', re.IGNORECASE)

        with open(self.args.in_path) as f:
            file_content = f.readlines()
            f.close()

        # strip surrounding whitespace and match against regex
        for line in file_content:
            # Skip empty lines
            if line != '\n':
                _line = line.strip()
                match = label_test.match(_line)

                if match:
                    # Update the label cache and/or instruction list
                    label = match.group('label')
                    instruction = match.group('instruction')
                    self.label_cache.write(label, self.pc)

                    if instruction:
                        self.instructions.append(instruction)
                        self.pc = self.pc + 1
                else:
                    # No match with label
                    # If spaces exist before the newline, we can have an empty string after stripping
                    if _line:
                        self.instructions.append(_line)
                        self.pc = self.pc + 1

        self.process_instructions()

    def process_instructions(self):
        """ Encode each instruction as a bitstring, overwriting the previous, non-encoded, instruction. """
        for index, inst in enumerate(self.instructions):
            bitstring = self.encoder.encode_instruction(index, inst)
            self.instructions[index] = bitstring

    def write(self):
        # Write instruction memory to file
        # TODO: this can be customized using an output "formatter"
        out = open(self.args.out_path, 'w')
        for instruction in self.instructions:
            out.write('{}\n'.format(instruction))

        out.close()


if __name__ == '__main__':
    assembler = MIPSAssembler()

    try:
        assembler.run()
        assembler.write()
    except Exception, e:
        print e.message
