import sys
import os

class Parser:
    # reads VM commands and breaks them into components

    def __init__(self, filename):
        # load and clean VM file
        self.commands = []
        self.current_command = ""
        self.command_index = -1

        with open(filename, 'r') as file:
            for line in file:
                line = line.strip()
                # remove comments
                if '//' in line:
                    line = line[:line.index('//')]
                line = line.strip()
                # skip empty lines
                if line:
                    self.commands.append(line)

    def hasMoreCommands(self):
        return self.command_index + 1 < len(self.commands)

    def advance(self):
        # move to next command
        if self.hasMoreCommands():
            self.command_index += 1
            self.current_command = self.commands[self.command_index]

    def commandType(self):
        # identify command type
        parts = self.current_command.split()
        command = parts[0]

        if command in ['add', 'sub', 'neg', 'eq', 'gt', 'lt', 'and', 'or', 'not']:
            return 'C_ARITHMETIC'
        elif command == 'push':
            return 'C_PUSH'
        elif command == 'pop':
            return 'C_POP'
        else:
            return 'C_UNKNOWN'

    def arg1(self):
        # extract first arg
        if self.commandType() == 'C_ARITHMETIC':
            return self.current_command.split()[0]
        else:
            return self.current_command.split()[1]

    def arg2(self):
        # extract second arg for push/pop
        if self.commandType() in ['C_PUSH', 'C_POP']:
            return int(self.current_command.split()[2])
        return None


class CodeWriter:
    # generates assembly from VM commands

    def __init__(self, output_file):
        # init code writer
        self.output_file = open(output_file, 'w')
        self.label_counter = 0
        self.filename = None

    def setFileName(self, filename):
        # set current filename for static vars
        self.filename = os.path.splitext(os.path.basename(filename))[0]

    def writeArithmetic(self, command):
        # write arithmetic command
        if command == 'add':
            self.writeBinaryOp('+')
        elif command == 'sub':
            self.writeBinaryOp('-')
        elif command == 'and':
            self.writeBinaryOp('&')
        elif command == 'or':
            self.writeBinaryOp('|')
        elif command == 'neg':
            self.writeUnaryOp('-')
        elif command == 'not':
            self.writeUnaryOp('!')
        elif command == 'eq':
            self.writeComparison('JEQ')
        elif command == 'gt':
            self.writeComparison('JGT')
        elif command == 'lt':
            self.writeComparison('JLT')

    def writePushPop(self, command, segment, index):
        # write push or pop command
        if command == 'C_PUSH':
            self.writePush(segment, index)
        elif command == 'C_POP':
            self.writePop(segment, index)

    def writeBinaryOp(self, op):
        # binary arithmetic operation
        # pop y to D
        self.popToD()
        # pop x, compute x op y
        self.output_file.write("@SP\n")
        self.output_file.write("AM=M-1\n")
        if op == '+':
            self.output_file.write("M=M+D\n")
        elif op == '-':
            self.output_file.write("M=M-D\n")
        elif op == '&':
            self.output_file.write("M=M&D\n")
        elif op == '|':
            self.output_file.write("M=M|D\n")
        # inc stack pointer
        self.incSP()

    def writeUnaryOp(self, op):
        # unary arithmetic operation
        self.output_file.write("@SP\n")
        self.output_file.write("A=M-1\n")
        if op == '-':
            self.output_file.write("M=-M\n")
        elif op == '!':
            self.output_file.write("M=!M\n")

    def writeComparison(self, jumpType):
        # comparison operation
        # pop y to D
        self.popToD()
        # pop x
        self.output_file.write("@SP\n")
        self.output_file.write("AM=M-1\n")
        # compute x - y
        self.output_file.write("D=M-D\n")
        trueLabel = f"TRUE_{self.label_counter}"
        endLabel = f"END_{self.label_counter}"
        self.label_counter += 1

        # jump if condition
        self.output_file.write(f"@{trueLabel}\n")
        self.output_file.write(f"D;{jumpType}\n")
        # false case: push 0
        self.output_file.write("@SP\n")
        self.output_file.write("A=M\n")
        self.output_file.write("M=0\n")
        self.output_file.write(f"@{endLabel}\n")
        self.output_file.write("0;JMP\n")
        # true case: push -1
        self.output_file.write(f"({trueLabel})\n")
        self.output_file.write("@SP\n")
        self.output_file.write("A=M\n")
        self.output_file.write("M=-1\n")
        self.output_file.write(f"({endLabel})\n")
        # inc stack pointer
        self.incSP()

    def writePush(self, segment, index):
        # push command
        if segment == 'constant':
            # push constant val
            self.output_file.write(f"@{index}\n")
            self.output_file.write("D=A\n")
            self.pushD()
        elif segment == 'local':
            self.pushFromSeg("LCL", index)
        elif segment == 'argument':
            self.pushFromSeg("ARG", index)
        elif segment == 'this':
            self.pushFromSeg("THIS", index)
        elif segment == 'that':
            self.pushFromSeg("THAT", index)
        elif segment == 'temp':
            # temp starts at RAM[5]
            self.output_file.write(f"@{5 + index}\n")
            self.output_file.write("D=M\n")
            self.pushD()
        elif segment == 'pointer':
            # pointer 0=THIS, 1=THAT
            if index == 0:
                self.output_file.write("@THIS\n")
            else:
                self.output_file.write("@THAT\n")
            self.output_file.write("D=M\n")
            self.pushD()
        elif segment == 'static':
            # static vars are filename.index
            self.output_file.write(f"@{self.filename}.{index}\n")
            self.output_file.write("D=M\n")
            self.pushD()

    def writePop(self, segment, index):
        # pop command
        if segment == 'local':
            self.popToSeg("LCL", index)
        elif segment == 'argument':
            self.popToSeg("ARG", index)
        elif segment == 'this':
            self.popToSeg("THIS", index)
        elif segment == 'that':
            self.popToSeg("THAT", index)
        elif segment == 'temp':
            # pop to temp
            self.popToD()
            # !! ram starts at address 5 !!
            self.output_file.write(f"@{5 + index}\n")
            self.output_file.write("M=D\n")
        elif segment == 'pointer':
            # pop to pointer
            self.popToD()
            if index == 0:
                self.output_file.write("@THIS\n")
            else:
                self.output_file.write("@THAT\n")
            self.output_file.write("M=D\n")
        elif segment == 'static':
            # pop to static var
            self.popToD()
            # use filename here because static vars need unique names
            self.output_file.write(f"@{self.filename}.{index}\n")
            self.output_file.write("M=D\n")

    def pushFromSeg(self, segName, index):
        # push val from memory segment
        # get base addr
        self.output_file.write(f"@{segName}\n")
        self.output_file.write("D=M\n")
        # add index
        self.output_file.write(f"@{index}\n")
        self.output_file.write("A=D+A\n")
        # get val
        self.output_file.write("D=M\n")
        self.pushD()

    def popToSeg(self, segName, index):
        # pop val to memory segment
        # compute target addr in R13
        self.output_file.write(f"@{segName}\n")
        self.output_file.write("D=M\n")
        self.output_file.write(f"@{index}\n")
        self.output_file.write("D=D+A\n")
        self.output_file.write("@R13\n")
        self.output_file.write("M=D\n")
        # pop val
        self.popToD()
        # store in target
        self.output_file.write("@R13\n")
        self.output_file.write("A=M\n")
        self.output_file.write("M=D\n")

    def pushD(self):
        # push D onto stack
        self.output_file.write("@SP\n")
        self.output_file.write("A=M\n")
        self.output_file.write("M=D\n")
        self.incSP()

    def popToD(self):
        # pop stack to D
        self.output_file.write("@SP\n")
        self.output_file.write("AM=M-1\n")
        self.output_file.write("D=M\n")

    def incSP(self):
        # inc stack pointer
        self.output_file.write("@SP\n")
        self.output_file.write("M=M+1\n")

    def close(self):
        self.output_file.close()


def translateVMFile(vmFile, asmFile):
    # translate single VM file to assembly
    parser = Parser(vmFile)
    codeWriter = CodeWriter(asmFile)
    codeWriter.setFileName(vmFile)

    while parser.hasMoreCommands():
        parser.advance()
        cmdType = parser.commandType()

        if cmdType == 'C_ARITHMETIC':
            codeWriter.writeArithmetic(parser.arg1())
        elif cmdType in ['C_PUSH', 'C_POP']:
            codeWriter.writePushPop(cmdType, parser.arg1(), parser.arg2())

    codeWriter.close()


def main():
    # translate VM file or directory to assembly
    # if len(sys.argv) != 2:
    #     print("Usage: python hvm.py <file_or_directory>")
    #     sys.exit(1)

    inputPath = sys.argv[1]

    if not os.path.exists(inputPath):
        print(f"Error: Path '{inputPath}' not found")
        sys.exit(1)

    if os.path.isfile(inputPath):
        # single file mode
        if not inputPath.endswith('.vm'):
            print("Error: Input file must have .vm extension")
            sys.exit(1)

        outputFile = inputPath[:-3] + '.asm'
        translateVMFile(inputPath, outputFile)
        print(f"Translated '{inputPath}' to '{outputFile}'")

    elif os.path.isdir(inputPath):
        # directory mode - find VM file in directory
        vmFiles = [f for f in os.listdir(inputPath) if f.endswith('.vm')]

        if not vmFiles:
            print(f"Error: No .vm files found in directory '{inputPath}'")
            sys.exit(1)

        # find VM file with same name as directory
        dirName = os.path.basename(inputPath.rstrip('/'))
        vmFileName = dirName + '.vm'

        if vmFileName in vmFiles:
            vmFile = os.path.join(inputPath, vmFileName)
        else:
            # use first VM file found
            vmFile = os.path.join(inputPath, vmFiles[0])

        outputFile = os.path.join(inputPath, dirName + '.asm')
        translateVMFile(vmFile, outputFile)
        print(f"Translated '{vmFile}' to '{outputFile}'")

    else:
        print(f"Error: '{inputPath}' is neither file nor directory")
        sys.exit(1)

if __name__ == "__main__":
    main()
