import pygame
import sys
from ctypes import *
import shlex
import ast
import os
__Version__="0.1"
if sys.platform == "win32":
    from ctypes import windll
    try:
        windll.shcore.SetProcessDpiAwareness(1)
    except:
        pass
class screen:
    def __init__(self,font="consolas",fontsize=24):
        pygame.init()
        pygame.display.set_caption("<Undefined ARCH>")
        self.clock=pygame.time.Clock()
        self.clock.tick(60)
        self.screen = pygame.display.set_mode((600, 600))
        self.font = pygame.font.SysFont(font, fontsize)
        self.pixel_layer=pygame.Surface(self.screen.get_size(), pygame.SRCALPHA)
        self.printBuffer=[]
        self.y=0
        self.held_keys = {}
        self.txtlog=[]
        self.colorlog=[]
        self.endlog=[]
        self.xlog=[]
        self.pixellog=[]
        self.surfaces={}
        self.ylog=[]
    def text(self,txt,x,color=(255,255,255),AddToTxtLog=True,end="\n",customy=(False,0)):
        lines=txt.split("\n")
        for line in lines:
            if customy[0]==True:
                self.screen.blit(self.font.render(line, True, color),(x,customy[1]))
            else:
                self.screen.blit(self.font.render(line, True, color), (x, self.y))
            if end=="\n" and customy[0]==False:
                self.y+=20
            if AddToTxtLog:
                self.txtlog.append(line)
                self.colorlog.append(color)
                self.endlog.append(end)
                self.xlog.append(x)
                if customy[0]==True:
                    self.ylog.append(customy)
                else:
                    self.ylog.append((False,self.y))
    def clear(self):
        self.screen.fill((0, 0, 0))
        self.pixel_layer.fill((0,0,0,0))
        self.y=0
    def events(self):
        ES = "n"
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                key_name = pygame.key.name(event.key)
                ES = f"kDWN{key_name}"
                self.held_keys[key_name] = True
            elif event.type == pygame.KEYUP:
                key_name = pygame.key.name(event.key)
                ES = f"kUP{key_name}"
                self.held_keys[key_name] = False
            elif event.type == pygame.MOUSEBUTTONDOWN:
                ES = ("mDWN",pygame.mouse.get_pos())
            elif event.type == pygame.MOUSEBUTTONUP:
                ES = ("mUP",pygame.mouse.get_pos())
        return ES
    def isHeld(self, kName):
        return self.held_keys.get(kName, False)
    def input(self, prompt="", promptcolor=(255, 255, 255), promtend="", promptx = 0):
        itext = ""
        basey=self.y
        blink=""
        blinktime=0
        while True:
            self.clear()
            self.loadpixels()
            i=0
            for line in self.txtlog:
                display = "".join(line)
                color=self.colorlog[i]
                end=self.endlog[i]
                x=self.xlog[i]
                y=self.ylog[i]
                i+=1
                self.text(display, x,color,False,end=end,customy=(y))
            i2=0
            for j in self.pixellog:
                px = self.pixellog[i2][0][0]
                py = self.pixellog[i2][0][1]
                pcol = self.pixellog[i2][1]
                psize = self.pixellog[i2][2]
                pgroup = self.pixellog[i2][3]
                self.pixel(px, py, pcol, psize, pgroup, False)
                i2+=1
            self.text(prompt + itext+blink, promptx, promptcolor, False, end=promtend,)  # display current input
            self.loadpixels()
            pygame.display.flip()
            event=pygame.event.wait()
            if event.type == pygame.QUIT:
                sys.exit()  # user closed the window
            elif event.type == pygame.KEYDOWN:
                key_name = pygame.key.name(event.key)
                if key_name == "backspace":
                    itext = itext[:-1]
                elif key_name == "return":
                    return itext
                elif key_name == "space":
                    itext += " "
                elif len(key_name) == 1:
                    itext += self.keyNameWithShift(event)
    def clearlogs(self):
            self.txtlog=[]
            self.colorlog=[]
            self.endlog=[]
            self.xlog=[]
            self.pixellog=[]
            self.surfaces={}
            self.ylog=[]
    def keyNameWithShift(self, event):
        key_name = pygame.key.name(event.key)
        shift_held = pygame.key.get_mods() & pygame.KMOD_SHIFT
        if key_name.isalpha() and shift_held:
            key_name = key_name.upper()
        shift_map = {
            '1': '!', '2': '@', '3': '#', '4': '$', '5': '%',
            '6': '^', '7': '&', '8': '*', '9': '(', '0': ')',
            '-': '_', '=': '+', '[': '{', ']': '}', '\\': '|',
            ';': ':', "'": '"', ',': '<', '.': '>', '/': '?',
            '`': '~'
        }
        if key_name in shift_map and shift_held:
            key_name = shift_map[key_name]
        return key_name
    def newframe(self):
        self.rendersurfaces()
        self.screen.blit(self.pixel_layer, (0, 0))
        pygame.display.flip()
        self.clock.tick(60)
    def pixel(self, x=0, y=0, color=(255, 255, 255),size=1,group=None,addtopixellog=True):
        pygame.draw.rect(self.pixel_layer, color, (x, y, size, size))
        if addtopixellog==True:
            self.pixellog.append(((x,y), color, size,group))
        else:
            pass
    def loadpixels(self):
        self.screen.blit(self.pixel_layer, (0, 0))
    def MouseClickedOnGroup(self, event, group=None):
        if isinstance(event, tuple) and event[0] == "mDWN":
            mouse_x, mouse_y = event[1]
            for px, color, size, pxgroup in self.pixellog:
                if pxgroup == group:
                    px_x, px_y = px
                    if px_x <= mouse_x < px_x + size and px_y <= mouse_y < px_y + size:
                        return True
        return False
    def MousePos(self):
        return pygame.mouse.get_pos()
    def showmouse(self,visibility=True):
        pygame.mouse.set_visible(visibility)
    def quit(self):
        pygame.quit()
        sys.exit()
    def addsurface(self,name,width,height,col=(0,0,255),posx=0,posy=0):
        self.surfaces[name]={}
        self.surfaces[name]["data"]=pygame.Surface((width,height),pygame.SRCALPHA)
        self.surfaces[name]["data"].fill(col)
        self.surfaces[name]["pos"]=(posx,posy)
        self.surfaces[name]["rect"]= self.surfaces[name]["data"].get_rect(topleft=(posx, posy))
    def rendersurfaces(self):
        for name in self.surfaces:
            self.screen.blit(self.surfaces[name]["data"], self.surfaces[name]["pos"])
    def mouseTouchingSurface(self, name):
        mouse_pos = pygame.mouse.get_pos()
        return self.surfaces[name]["rect"].collidepoint(mouse_pos)
    def wait(self,t):
        pygame.time.wait(t)
    def scroll(self,dx,dy):
        self.screen.scroll(dx,dy)
    def SetWindowName(self,name):
        pygame.display.set_caption(f"{name}")
class cpu:
    def __init__(self,Arch_name="Undefined ARCH"):
        self.registers={}
        self.ram={}
        self.opcodes={}
        self.pc=0
        self.MaxValue=0xffffffff
        self.program=[]
        self.sc=screen()
        self.comment_symbol=";"
        self.Arch_name=Arch_name
        self.sc.SetWindowName(Arch_name)
        self.BuildCommands={}
    #-/main/-#
    def main(self):
        self.set_window_name("<ANGEL>")
        self.text("welcome to Angel-CPU",0)
        self.frame()
        self.text("Type CMD for a list of commands",0)
        self.frame()
        while True:
            i=self.text_input(">",(255,0,0))
            if i == "CMD":
                self.clear()
                self.clear_logs()
                self.text("CMD: lists commands",0)
                self.text("INFO: Displays Information about Angel",0)
                self.text("Version: Displays Angel's Version",0)
                self.frame()
                self.text_input("press enter to continue...")
            elif i == "INFO":
                self.clear()
                self.clear_logs()
                self.text("<Angel>: A CPU framework for",0)
                self.text("building virtual CPUs in python,",0)
                self.text("You can find more information about",0)
                self.text("Angel in the README file.",0)
                self.frame()
                self.text_input("press enter to continue...")
            elif i == "Version":
                self.text(f"Angel Version:{__Version__}",0)
                self.text_input("press enter to continue...")
                self.clear_logs()
                self.clear()
    #-/add's/-#
    def add_register(self,register):
        self.registers[register]=0x0
    def add_ram(self,size_x,size_y):
        self.ram=[0x0]*size_x*size_y
    def add_opcode(self,opcode,command):
        self.opcodes[opcode]=command
    def add_instruction(self,instruction):
        self.program.append(instruction)
    #-/set's/-#
    def set_window_name(self,name):
        self.sc.SetWindowName(name)
    def set_max(self,max):
        self.MaxValue=max
    def set_pc(self,pc):
        self.pc=pc
    def set_register(self,register,value):
        if type(value) == int:
            if value <= self.MaxValue:
                self.registers[register]=value
            else:
                self.registers[register]=0x0
        elif type(value) == str:
            if len(value) <= self.MaxValue:
                self.registers[register]=value
            else:
                self.registers[register]=""
        elif type(value) == tuple:
            if len(value) <= self.MaxValue:
                self.registers[register]=value
            else:
                self.registers[register]=()
        elif type(value) == list:
            if len(value) <= self.MaxValue:
                self.registers[register]=value
            else:
                self.registers[register]=[]
        elif type(value) == dict:
            if len(value) <= self.MaxValue:
                self.registers[register]=value
            else:
                self.registers[register]={}
    def set_ram(self,addr,value):
        if type(addr) == int:
            if addr <= self.MaxValue:
                self.ram[addr]=value
            else:
                self.ram[addr]=0x0
        if type(addr) == str:
            if len(addr) <= self.MaxValue:
                self.ram[addr]=value
            else:
                self.ram[addr]=""
        if type(addr) == tuple:
            if len(addr) <= self.MaxValue:
                self.ram[addr]=value
            else:
                self.ram[addr]=()
        if type(addr) == list:
            if len(addr) <= self.MaxValue:
                self.ram[addr]=value
            else:
                self.ram[addr]=[]
        if type(addr) == dict:
            if len(addr) <= self.MaxValue:
                self.ram[addr]=value
            else:
                self.ram[addr]={}
    def reset(self):
        self.pc=0
        self.program=[]
    def set_comment_symbol(self,symbol):
        self.comment_symbol=symbol
    #-/Get's/-#
    def get_register(self,register):
        return self.registers[register]
    def get_ram(self,addr):
        return self.ram[addr]
    def get_pc(self):
        return self.pc
    #-/screen/-#
    def text(self, txt, x, color=(255, 255, 255), AddToTxtLog=True, end="\n", customy=(False, 0)):
        self.sc.text(txt, x, color, AddToTxtLog, end, customy)
    def wait(self,t):
        self.sc.wait(t)
    def scroll(self,dx,dy):
        self.sc.scroll(dx,dy)
    def is_Mouse_Touching_Surface(self,name):
        return self.sc.mouseTouchingSurface(name)
    def show_mouse(self,visibility=True):
        self.sc.showmouse(visibility)
    def quit(self):
        pygame.quit()
        sys.exit()
    def frame(self):
        self.sc.newframe()
    def load_pixels(self):
        self.sc.loadpixels()
    def events(self):
        self.sc.events()
    def clear(self):
        self.sc.clear()
    def clear_logs(self):
        self.sc.clearlogs()
    def is_held(self,key):
        return self.sc.isHeld(key)
    def render_surfaces(self):
        self.sc.rendersurfaces()
    def add_surface(self,name,width,height,col=(0,0,255),posx=0,posy=0):
        self.sc.addsurface(name,width,height,col,posx,posy)
    def add_pixel(self, x=0, y=0, color=(255, 255, 255),size=1,group=None,addtopixellog=True):
        self.sc.pixel(x,y,color,size,group,addtopixellog)
    def text_input(self, prompt="", promptcolor=(255, 255, 255), promtend="", promptx = 0):
        return self.sc.input(prompt,promptcolor,promtend,promptx)
    def mouse_down_on_group(self,event,group=None):
        return self.sc.MouseClickedOnGroup(event,group)
    def mouse_pos(self):
        return self.sc.MousePos()
    #-/assembler/-#
    def assemble(self, code):
        lines = code.strip().split("\n")
        name_to_opcode = {}
        for opcode, func in self.opcodes.items():
            name_to_opcode[func.__name__.upper()] = opcode
        for line in lines:
            line = line.strip()
            if line.startswith(self.comment_symbol):
                continue
            if not line:
                continue
            parts = shlex.split(line)
            inst_name = parts[0].upper()
            args = []
            for arg in parts[1:]:
                try:
                    value = ast.literal_eval(arg)
                except:
                    value = arg
                args.append(value)
            if inst_name in name_to_opcode:
                self.add_instruction((name_to_opcode[inst_name], tuple(args)))
            else:
                raise Exception(f"Unknown instruction: {inst_name}")
    #-/run/-#
    def run(self):
        while self.pc<len(self.program):
            command=self.program[self.pc][0]
            args=self.program[self.pc][1]
            self.pc+=1
            for opcode in self.opcodes:
                if opcode==command:
                    self.opcodes[opcode](*args)
def main():
    Angel = cpu("<Angel>")
    Angel.main()
if __name__ == "__main__":
    main()