# coding: utf-8

"""
Ctypes wrapper module for BUSMUST CAN Interface on win32/win64 systems.

Authors: busmust <busmust@126.com>, BUSMUST Co.,Ltd.
"""

# Import Standard Python Modules
# ==============================
import ctypes
import logging
import sys
import time

try:
    # Try builtin Python 3 Windows API
    from _winapi import WaitForSingleObject, INFINITE
    HAS_EVENTS = True
except ImportError:
    try:
        # Try pywin32 package
        from win32event import WaitForSingleObject, INFINITE
        HAS_EVENTS = True
    except ImportError:
        # Use polling instead
        HAS_EVENTS = False

# Import Modules
# ==============
from can import BusABC, Message, CanError
from can.bus import BusState
from can.util import len2dlc, dlc2len
from .exceptions import BmError

# Define Module Logger
# ====================
LOG = logging.getLogger(__name__)

# Import safely Vector API module for Travis tests
bmapi = None
try:
    from . import bmapi
except Exception as exc:
    LOG.warning('Could not import bmapi: %s', exc)

class BmCanBus(BusABC):
    """The CAN Bus implemented for the BUSMUST USB-CAN interface."""

    __initialized = False

    @classmethod
    def __init_class__(cls):
        if bmapi is None:
            raise ImportError("The BMAPI has not been loaded")
        if not BmCanBus.__initialized:
            bmapi.BM_Init()
            BmCanBus.__initialized = True

    def __init__(self, channel,
                 fd=True, receive_own_messages=False, listen_only=False,
                 bitrate=500000, data_bitrate=500000,
                 tres=False,
                 can_filters=None,
                 **kwargs):
        """
        :param int channel:
            The channel index to create this bus with, which is the index to all available ports when enumerating Busmust devices.
            Can also be a string of the channel's full name. i.e. "BM-CANFD-X1-PRO(1234) CH1"
        :param bool fd:
            If CAN-FD frames should be supported.
        :param bool receive_own_messages:
            If Loopback mode should be supported.
        :param bool listen_only:
            If Listen only mode should be supported, this is the same as setting 'state' property to INACTIVE.
        :param int bitrate:
            Bitrate in bits/s.
        :param int data_bitrate:
            Which bitrate to use for data phase in CAN FD.
            Defaults to arbitration bitrate.
        :param bool tres:
            If 120Ohm CAN terminal register should be enabled.
        """
        BmCanBus.__init_class__()
        self._bmapi = bmapi # Enable external access

        infolist = bmapi.BM_ChannelInfoListTypeDef()
        numOfInfo = ctypes.c_int(len(infolist.entries))
        bmapi.BM_Enumerate(ctypes.byref(infolist), ctypes.byref(numOfInfo))
        if isinstance(channel, int):
            if channel < numOfInfo.value:
                self._channelinfo = infolist.entries[channel]
            else:
                raise BmError(bmapi.BM_ERROR_NODRIVER, "Channel %d is not connected or is in use by another app." % channel, "BmCanBus.__init__")
        elif isinstance(channel, str):
            for info in infolist.entries:
                if info.name.decode() == channel:
                    self._channelinfo = info
                    break
            else:
                raise BmError(bmapi.BM_ERROR_NODRIVER, "Channel %s is not connected or is in use by another app." % channel, "BmCanBus.__init__")

        self._mode = bmapi.BM_CAN_NORMAL_MODE
        if not fd:
            self._mode = bmapi.BM_CAN_CLASSIC_MODE
        elif receive_own_messages:
            self._mode = bmapi.BM_CAN_EXTERNAL_LOOPBACK_MODE
        elif listen_only:
            self._mode = bmapi.BM_CAN_LISTEN_ONLY_MODE

        self._tres = bmapi.BM_TRESISTOR_DISABLED
        if tres:
            self._tres = bmapi.BM_TRESISTOR_120

        self._bitrate = bmapi.BM_BitrateTypeDef()
        self._bitrate.nbitrate = int(bitrate / 1000)
        self._bitrate.dbitrate = int(data_bitrate / 1000)

        self._handle = bmapi.BM_ChannelHandle()
        bmapi.BM_OpenEx(
            ctypes.byref(self._handle),
            ctypes.byref(self._channelinfo),
            self._mode,
            self._tres,
            ctypes.byref(self._bitrate),
            ctypes.cast(ctypes.c_void_p(), ctypes.POINTER(bmapi.BM_RxFilterListTypeDef)), 0
        )
        self.channel_info = self._channelinfo.name.decode()

        startTimestamp = ctypes.c_uint32()
        bmapi.BM_GetTimestamp(self._handle, ctypes.byref(startTimestamp))
        self._time_offset = time.time() - startTimestamp.value * 1e-9

        self._notification = bmapi.BM_NotificationHandle()
        bmapi.BM_GetNotification(self._handle, ctypes.byref(self._notification))

        super(BmCanBus, self).__init__(channel=channel, can_filters=can_filters, **kwargs)
        time.sleep(0.05)

        self._state = BusState.ACTIVE

        self._isotp_config = bmapi.BM_IsotpConfigTypeDef()

    def _apply_filters(self, filters):
        if filters:
            # Only up to one filter per ID type allowed
            if len(filters) == 1 or (len(filters) == 2 and filters[0].get("extended") != filters[1].get("extended")):
                bmfilters = bmapi.BM_RxFilterListTypeDef()
                try:
                    for i in range(len(filters)):
                        can_filter = filters[i]
                        bmfilters.entries[i].type = bmapi.BM_RXFILTER_BASIC
                        bmfilters.entries[i].flags_mask = bmapi.BM_MESSAGE_FLAGS_IDE
                        if can_filter.get("extended"):
                            bmfilters.entries[i].flags_value = bmapi.BM_MESSAGE_FLAGS_IDE
                            bmfilters.entries[i].id_mask = (can_filter["can_mask"] >> 18) | ((can_filter["can_mask"] & 0x3FFFF) << 11)
                            bmfilters.entries[i].id_value = (can_filter["can_id"] >> 18) | ((can_filter["can_id"] & 0x3FFFF) << 11)
                        else:
                            bmfilters.entries[i].flags_value = 0
                            bmfilters.entries[i].id_mask = can_filter["can_mask"]
                            bmfilters.entries[i].id_value = can_filter["can_id"]
                    bmapi.BM_SetRxFilters(self._handle, ctypes.byref(bmfilters), len(bmfilters.entries))
                    time.sleep(0.05)
                except BmError as exc:
                    LOG.warning("Could not set filters: %s", exc)
                    # go to fallback
                else:
                    self._is_filtered = True
                    return
            else:
                LOG.warning("Only up to one filter per extended or standard ID allowed")
                # go to fallback

        # fallback: reset filters
        self._is_filtered = False
        try:
            bmfilters = bmapi.BM_RxFilterListTypeDef() # Default as invalid => allow all messages to pass
            bmapi.BM_SetRxFilters(self._handle, ctypes.byref(bmfilters), 2)
            time.sleep(0.05)
        except BmError as exc:
            LOG.warning("Could not reset filters: %s", exc)

    def _recv_internal(self, timeout):
        end_time = time.time() + timeout if timeout is not None else None

        bmmsg = bmapi.BM_CanMessageTypeDef()
        channel = ctypes.c_uint32()
        timestamp = ctypes.c_uint32()

        while True:
            try:
                bmapi.BM_ReadCanMessage(self._handle, ctypes.byref(bmmsg), ctypes.byref(channel), ctypes.byref(timestamp))
            except BmError as exc:
                if exc.error_code != bmapi.BM_ERROR_QRCVEMPTY:
                    raise
            else:
                msg_id = bmmsg.mid.getExtendedId() if bmmsg.ctrl.rx.IDE else bmmsg.mid.getStandardId()
                dlc = dlc2len(bmmsg.ctrl.rx.DLC)
                msg = Message(
                    timestamp=timestamp.value * 1e-6 + self._time_offset,
                    arbitration_id=msg_id & 0x1FFFFFFF,
                    is_extended_id=bool(bmmsg.ctrl.rx.IDE),
                    is_remote_frame=bool(bmmsg.ctrl.rx.RTR),
                    is_error_frame=bool(False),
                    is_fd=bool(bmmsg.ctrl.rx.FDF),
                    error_state_indicator=bool(bmmsg.ctrl.rx.ESI),
                    bitrate_switch=bool(bmmsg.ctrl.rx.BRS),
                    dlc=dlc,
                    data=bmmsg.payload[:dlc],
                    channel=channel.value)
                return msg, self._is_filtered

            if end_time is not None and time.time() > end_time:
                return None, self._is_filtered

            # Wait for receive event to occur
            if timeout is None:
                time_left_ms = INFINITE
            else:
                time_left = end_time - time.time()
                time_left_ms = max(0, int(time_left * 1000))
            bmapi.BM_WaitForNotifications(ctypes.byref(self._notification), 1, time_left_ms)

    def send(self, msg, timeout=None):
        bmmsg = bmapi.BM_CanMessageTypeDef()
        if msg.is_extended_id:
            bmmsg.mid.SID = msg.arbitration_id >> 18
            bmmsg.mid.EID = (msg.arbitration_id & 0x3FFFF)
        else:
            bmmsg.mid.SID = msg.arbitration_id
            bmmsg.mid.EID = 0
        bmmsg.ctrl.tx.IDE = 1 if msg.is_extended_id else 0
        bmmsg.ctrl.tx.FDF = 1 if msg.is_fd else 0
        bmmsg.ctrl.tx.BRS = 1 if msg.bitrate_switch else 0
        bmmsg.ctrl.tx.RTR = 1 if msg.is_remote_frame else 0
        bmmsg.ctrl.tx.ESI = 1 if msg.error_state_indicator else 0
        bmmsg.ctrl.tx.DLC = len2dlc(msg.dlc)
        bmmsg.payload[:len(msg.data)] = msg.data
        timestamp = ctypes.c_uint32()
        bmapi.BM_WriteCanMessage(self._handle, ctypes.byref(bmmsg), 0, int(timeout*1000) if timeout else -1, ctypes.byref(timestamp))

    def shutdown(self):
        bmapi.BM_Close(self._handle)
        self._handle = bmapi.BM_ChannelHandle()

    def reset(self):
        bmapi.BM_Reset(self._handle)

    @property
    def state(self):
        return self._state

    @state.setter
    def state(self, new_state):
        mode_changed = False
        self._state = new_state
        if new_state is BusState.ACTIVE:
            if self._mode == bmapi.BM_CAN_OFF_MODE or self._mode == bmapi.BM_CAN_LISTEN_ONLY_MODE:
                self._mode = bmapi.BM_CAN_NORMAL_MODE
                mode_changed = True
            else:
                pass # Do not change (i.e. loopback)
        elif new_state is BusState.PASSIVE:
            # When this mode is set, the CAN controller does not take part on active events (eg. transmit CAN messages)
            # but stays in a passive mode (CAN monitor), in which it can analyse the traffic on the CAN bus used by a BMCAN channel.
            if self._mode != bmapi.BM_CAN_LISTEN_ONLY_MODE:
                self._mode = bmapi.BM_CAN_LISTEN_ONLY_MODE
                mode_changed = True
        if mode_changed:
            bmapi.BM_SetCanMode(self._handle, self._mode)

    @classmethod
    def enumerate(cls):
        BmCanBus.__init_class__()
        infolist = bmapi.BM_ChannelInfoListTypeDef()
        numOfInfo = ctypes.c_int(len(infolist.entries))
        bmapi.BM_Enumerate(ctypes.byref(infolist), ctypes.byref(numOfInfo))
        channellist = []
        for i in range(numOfInfo.value):
            channellist.append({
                'index': i,
                'name': infolist.entries[i].name.decode(),
                # Add other exports here
            })
        return channellist

    def send_isotp(self, payload, timeout=-1):
        timeout_ms = int(timeout * 1000.0) if timeout >= 0 else -1
        bmapi.BM_WriteIsotp(self._handle, ctypes.c_char_p(payload), len(payload), timeout_ms, ctypes.byref(self._isotp_config))

    def receive_isotp(self, timeout=-1, max_len=4095):
        timeout_ms = int(timeout * 1000.0) if timeout >= 0 else -1
        buf = ctypes.create_string_buffer(max_len)
        received_len = ctypes.c_uint32(len(buf))
        bmapi.BM_ReadIsotp(self._handle, buf, ctypes.byref(received_len), timeout_ms, ctypes.byref(self._isotp_config))
        return bytes(buf[:received_len.value])

    def config_isotp(self, tester_msg_id, ecu_msg_id, mode=bmapi.BM_ISOTP_NORMAL_TESTER, **kwargs):
        self._isotp_config.mode = mode
        enable_fdf = kwargs.get('fd', False) or kwargs.get('fdf', False)
        enable_brs = kwargs.get('brs', enable_fdf)
        enable_ide = kwargs.get('ide', False) or tester_msg_id > 0x7FF or ecu_msg_id > 0x7FF
        dlc = kwargs.get('dlc', 0xF if enable_fdf else 0x8)
        testerMsg = self._isotp_config.testerDataTemplate.getCanMessage()
        testerMsg.ctrl.tx.FDF = enable_fdf
        testerMsg.ctrl.tx.BRS = enable_brs
        testerMsg.ctrl.tx.IDE = enable_ide
        testerMsg.ctrl.tx.DLC = dlc
        testerMsg.setMessageId(tester_msg_id)
        self._isotp_config.testerDataTemplate.setCanMessage(testerMsg)
        ecuMsg = self._isotp_config.ecuDataTemplate.getCanMessage()
        ecuMsg.ctrl.tx.FDF = enable_fdf
        ecuMsg.ctrl.tx.BRS = enable_brs
        ecuMsg.ctrl.tx.IDE = enable_ide
        ecuMsg.ctrl.tx.DLC = dlc
        ecuMsg.setMessageId(ecu_msg_id)
        self._isotp_config.ecuDataTemplate.setCanMessage(ecuMsg)
        padding = kwargs.get('padding', None)
        if padding is not None:
            self._isotp_config.paddingEnabled = 1
            self._isotp_config.paddingValue = ctypes.c_uint8(padding)
        else:
            self._isotp_config.paddingEnabled = 0


#-*- encoding: UTF-8 -*-
import urllib
from AlyMoly.reporte.excepciones import AbstractClassException
from AlyMoly.settings import REPORT_HOST, REPORT_PORT, REPORT_APP, REPORT_DIR,\
                     NOMBRE_SUCURSAL, CANTIDAD_PRODUCTOS_MAS_VENDIDOS,\
                    CANTIDAD_PROMOCIONES_MAS_VENDIDAS
from AlyMoly.reporte.conexion import URLRetriever
from django.http import HttpResponse
from django.core.urlresolvers import reverse

from HTMLParser import HTMLParser
import htmlentitydefs

class HTMLReportFixer(HTMLParser):
    """Cambia el título de una página html, a un titulo más institucional"""
    def __init__(self):
        HTMLParser.__init__(self)
        self.flag = False
        self.pieces = []

    def handle_starttag(self, tag, attrs):
        if tag == 'title':
            self.flag = True
        if tag == 'img':
            new_attrs = []
            for k,v in attrs:
                if k == 'src':
                    v = v.replace('/birt/preview',reverse('reporte:get_birt_img'))
                    new_attrs.append((k,v.encode()))
            attrs = new_attrs
        strattrs = "".join([' %s="%s"' % (key, value) for key, value in attrs])
        self.pieces.append("<%(tag)s%(strattrs)s>" % locals())

    def handle_endtag(self, tag):
        if tag == 'title':
            self.flag = False
        self.pieces.append("</%(tag)s>" % locals())

    def handle_data(self, data):
        if self.flag:
            data = "Reporte | %s" % (NOMBRE_SUCURSAL)
        self.pieces.append("%(data)s" % locals())

    def handle_charref(self,ref):
        self.pieces.append("&#%(ref)s;" % locals())

    def handle_comment(self, text):
        self.pieces.append("<!--%(text)s-->" % locals())

    def handle_entityref(self, ref):
        self.pieces.append("&%(ref)s" % locals())
        if htmlentitydefs.entitydefs.has_key(ref):
            self.pieces.append(";")

    def handle_pi(self, text):
        self.pieces.append("<?%(text)s>" % locals())

    def handle_decl(self, text):
        self.pieces.append("<!%(text)s>" % locals())

    def output(self):
        return "".join(self.pieces).replace("\n",'').strip()

class Reporte(object):
    """Clase abstracta padre que implementa la representacion un reporte genérico de
    archivo de reporte"""

    HTML = 'html'
    PDF = 'pdf'
    FORMATO = ( HTML, )

    def __new__(self, *args):
        if self is Reporte :
            raise AbstractClassException
        return object.__new__(self)

    def __init__(self,data):
        self.extension = "rptdesign"
        self.params = {}
        self.formato = data['formato']
    def __unicode__(self):
        return self.nombre + '.' + self.extension

    def get_url_params(self):
        list_params = []
        for key,val in self.params.items():
            list_params.append(key + "=" + urllib.quote(str(val)))
        url_params = "&".join(list_params)
        if url_params != '':
            return "&"+url_params
        return ''

    def get_filename(self):
        return "%s.%s" % (self.nombre,self.extension)

    def get_url(self):
        return ("http://%s:%s/%s/preview?__format=%s&__report=%s%s" % (REPORT_HOST,
                                                               REPORT_PORT,
                                                               REPORT_APP,
                                                               self.formato,
                                                               REPORT_DIR, self.get_filename())) + self.get_url_params()
    def get_response(self):
        retriever = URLRetriever(self.get_url())
        response = None
        if self.formato == 'pdf':
            response = HttpResponse(retriever.get(),mimetype='application/pdf')
            response['Content-Disposition'] = 'attachment; filename=%s.pdf' % self.__class__.__name__
        else:
            parser = HTMLReportFixer()
            parser.feed(retriever.get())
            response = HttpResponse(parser.output())
        return response

class ReportePeriodoTiempo(Reporte):

    def __new__(self, *args):
        if self is ReportePeriodoTiempo :
            raise AbstractClassException
        return object.__new__(self)

    def __init__(self,data):
        super(ReportePeriodoTiempo,self).__init__(data)
        fecha_inicio = 'fecha_inicio'
        fecha_fin = 'fecha_fin'
        if data.get('fecha',False):
            fecha_inicio = fecha_fin = 'fecha'
        self.params.update({'fecha_inicio':data[fecha_inicio].strftime('%Y-%m-%d'),
                            'fecha_fin':data[fecha_fin].strftime('%Y-%m-%d')})

##########       REPORTES        ##########

class Productos(Reporte):

    def __init__(self,data):
        super(Productos,self).__init__(data)
        self.nombre = "Productos"

class ProductosCodigoBarra(Reporte):

    def __init__(self,data):
        super(ProductosCodigoBarra,self).__init__(data)
        self.nombre = "ProductosCodigoBarra"

class ProductosCodigoManual(Reporte):

    def __init__(self,data):
        super(ProductosCodigoManual,self).__init__(data)
        self.nombre = "ProductosCodigoManual"

class ProductosExentos(Reporte):

    def __init__(self,data):
        super(ProductosExentos,self).__init__(data)
        self.nombre = "ProductosExentos"

class ProductosAfectos(Reporte):

    def __init__(self,data):
        super(ProductosAfectos,self).__init__(data)
        self.nombre = "ProductosAfectos"

class Promociones(Reporte):

    def __init__(self,data):
        super(Promociones,self).__init__(data)
        self.nombre = "Promociones"

class Existencias(Reporte):

    def __init__(self,data):
        super(Existencias,self).__init__(data)
        self.nombre = "Existencias"
        self.params.update({
                            'bodega_id':data['bodega'],
                            })

class ExistenciaPorCategoria(Reporte):

    def __init__(self,data):
        super(ExistenciaPorCategoria,self).__init__(data)
        self.nombre = "ExistenciasPorCategoria"
        self.params.update({
                            'bodega_id':data['bodega'],
                            'categoria_id':data['categoria']
                            })

class VentasPorTurno(Reporte):

    def __init__(self,data):
        super(VentasPorTurno,self).__init__(data)
        self.nombre = "EstadoDeTurno"
        self.params.update({
                            'turno_id':data['turno'],
                            })

class VentasPorTurnoDetalle(Reporte):

    def __init__(self,data):
        super(VentasPorTurnoDetalle,self).__init__(data)
        self.nombre = "VentasPorTurno"
        self.params.update({
                            'turno_id':data['turno'],
                            })

class VentasPorTurnoResumenAfectos(Reporte):

    def __init__(self,data):
        super(VentasPorTurnoResumenAfectos,self).__init__(data)
        self.nombre = "VentasPorTurnoResumenAfectos"
        self.params.update({
                            'turno_id':data['turno'],
                            })
class VentasPorTurnoResumenExentos(Reporte):

    def __init__(self,data):
        super(VentasPorTurnoResumenExentos,self).__init__(data)
        self.nombre = "VentasPorTurnoResumenExentos"
        self.params.update({
                            'turno_id':data['turno'],
                            })
class VentasPorTurnoResumenDevoluciones(Reporte):

    def __init__(self,data):
        super(VentasPorTurnoResumenDevoluciones,self).__init__(data)
        self.nombre = "VentasPorTurnoResumenDevoluciones"
        self.params.update({
                            'turno_id':data['turno'],
                            })
class VentasPorTurnoResumenPromociones(Reporte):

    def __init__(self,data):
        super(VentasPorTurnoResumenPromociones,self).__init__(data)
        self.nombre = "VentasPorTurnoResumenPromociones"
        self.params.update({
                            'turno_id':data['turno'],
                            })
class VentasPorTurnoResumenStockCritico(Reporte):

    def __init__(self,data):
        super(VentasPorTurnoResumenStockCritico,self).__init__(data)
        self.nombre = "VentasPorTurnoResumenStockCritico"
        self.params.update({
                            'turno_id':data['turno'],
                            })

class VentasPorMes(Reporte):

    def __init__(self,data):
        super(VentasPorMes,self).__init__(data)
        self.nombre = "VentaPorMes"
        fecha = data['anio'] + '-' + data['mes'] + '-01'
        self.params.update({
                            'mes_turno':fecha,
                            })

class VentasGraficoProducto(ReportePeriodoTiempo):

    def __init__(self,data):
        super(VentasGraficoProducto,self).__init__(data)
        self.nombre = "VentaGraficoProductosPorFechaPorCategorias"
        self.params.update({
                            'cantidad':CANTIDAD_PRODUCTOS_MAS_VENDIDOS,
                            })

class VentasGraficoPromocion(ReportePeriodoTiempo):

    def __init__(self,data):
        super(VentasGraficoPromocion,self).__init__(data)
        self.nombre = "VentaGraficoPromocionesPorFechaPorCategorias"
        self.params.update({
                            'cantidad':CANTIDAD_PRODUCTOS_MAS_VENDIDOS,
                            })

class VentasGraficoProductoPorCategoria(ReportePeriodoTiempo):

    def __init__(self,data):
        super(VentasGraficoProductoPorCategoria,self).__init__(data)
        self.nombre = "VentaGraficoProductosPorFechaPorCategoria"
        self.params.update({
                            'cantidad':CANTIDAD_PROMOCIONES_MAS_VENDIDAS,
                            'categoria_id':data.get("categoria"),
                            })

class VentasGraficoPromocionPorCategoria(ReportePeriodoTiempo):

    def __init__(self,data):
        super(VentasGraficoPromocionPorCategoria,self).__init__(data)
        self.nombre = "VentaGraficoPromocionesPorFechaPorCategoria"
        self.params.update({
                            'cantidad':CANTIDAD_PROMOCIONES_MAS_VENDIDAS,
                            'categoria_id':data.get("categoria"),
                            })

class VentasPorMesResumido(Reporte):

    def __init__(self,data):
        super(VentasPorMesResumido,self).__init__(data)
        self.nombre = "VentaPorMesResumido"
        fecha = data['anio'] + '-' + data['mes'] + '-01'
        self.params.update({
                            'mes_turno':fecha,
                            })


from const import *
import sys
from datetime import datetime
import numpy as np
import cv2
import os
from time import sleep
from threading import *
from utils import *

def removeTxt(txtBuffer):
    import os
    txtFileList = os.listdir(txtBuffer)
    for item in txtFileList:
        if item.endswith(".txt"):
            os.remove(os.path.join(txtBuffer, item))

def storeTxtToJpg(TXT_PATH,IMAGE_PATH,label,mode="dataCollection"):
    """
    TXT_PATH: folder name of the txt files
    IMAGE_PATH: destinated image folder to save, DO NOT include filename like .jpg
    mode: 'dataCollection' or 'prediction'

    """
    def arrayFromFile(file_name):
        arr=[]
        status=-1
        try:
            with open(file_name,'r') as f:
                for line in f.readlines(): #line is a string
                    line_arr=[float(x) for x in line.split(",")]
                    arr.append(line_arr)
            arr=np.array(arr)
            status=True
        except:
            status=False
        return arr, status
    def isEmptyTxt(file_name):
        return os.stat(f"{file_name}").st_size == 0
    def currentTimeInfo():
        return datetime.now().strftime("%Y%m%d_%H%M%S")
    #convert all txt files from {TXT_PATH} into jpg images and store them in {IMAGE_PATH}
    #1. list all files name with txt extension
    if mode=="dataCollection":
        destination_folder= os.path.join(IMAGE_PATH,f"{label}")
        for file in os.listdir(TXT_PATH):
            if file.endswith(".txt"):
                file_name=os.path.join(TXT_PATH,file)
                if not isEmptyTxt(file_name):
                    print(file_name)
                    img_array,result=arrayFromFile(file_name)
                    if (result==False or result==-1):
                        continue
                    if (result==-1):
                        raise Exception("error occur in arrayFromFile()")
                    sleep(1)

                    #cv2.imwrite(img_)
                    #path=os.path.join("images","your_file.jpg")
                    output_path=os.path.join(destination_folder,currentTimeInfo()+".jpg")
                    print(f"output_path={output_path}")
                    cv2.imwrite(output_path,img_array)
                    #print("complete image writing")
    elif mode=="prediction":
        destination_folder= os.path.join(IMAGE_PATH,f"{label}")
        for file in os.listdir(TXT_PATH):
            if file.endswith(".txt"):
                file_name=os.path.join(TXT_PATH,file)
                if not isEmptyTxt(file_name):
                    print(file_name)
                    img_array=arrayFromFile(file_name)
                    sleep(1)

                    #cv2.imwrite(img_)
                    #path=os.path.join("images","your_file.jpg")
                    output_path=os.path.join(destination_folder,"0.jpg")
                    print(f"output_path={output_path}")
                    cv2.imwrite(output_path,img_array)
                    print("complete image writing")
    else:
        raise Exception("INVALID MODE IN storeTxtToJpg()")
        quit()
def moveToBuffer(FromPath,ToPath):
    print("moving files from txtStorage to txtBuffer")
    source = FromPath
    destination = ToPath

    # gather all files
    allfiles = os.listdir(source)

    # iterate on all files to move them to destination folder
    for f in allfiles:
        src_path = os.path.join(source, f)
        dst_path = os.path.join(destination, f)
        os.rename(src_path, dst_path)

if __name__=='__main__':

    if len(sys.argv)!=3:#test 0
        raise Exception("""MISSING ARGUMENT(S)!!!
        python3 txtToJpg.py [train/test] [label]
        """)
        print("QUITTED")
        quit()
    if not (sys.argv[1]=='test' or sys.argv[1]=='train'):
        print("NO SUCH MODE, PLEASE ENTER test or train as mode")
        print("QUIT")
        quit()

    jsonAccesser=ConfJsonDictAccesser()
    conf_json_dict=jsonAccesser.get_dict()
    modelDataType=conf_json_dict["modelDataType"]

    if sys.argv[1]=='test':
        if modelDataType==jsonAccesser.DataLengthType.fix:
            path=FIXED_LENGTH_TEST_PATH
        elif modelDataType==jsonAccesser.DataLengthType.unfix:
            path=VARIED_LENGTH_TEST_PATH
    elif sys.argv[1]=='train':
        if modelDataType==jsonAccesser.DataLengthType.fix:
            path=FIXED_LENGTH_TRAIN_PATH
        elif modelDataType==jsonAccesser.DataLengthType.unfix:
            path=VARIED_LENGTH_TRAIN_PATH

    moveToBuffer(TXT_PATH,TXT_BUFFER)
    storeTxtToJpg(TXT_BUFFER,path,sys.argv[2])
    removeTxt(TXT_BUFFER)


from torchvision import models
import torch.nn.functional as nnf
import torch.nn as nn
import utilities
import config
import torch
import time
import sys
import os

class Classify():
    def __init__(self, logs):
        self.logs = logs
        self.modelPath = os.path.join(config.ROOT_DIRECTORY,config.MODEL_ROOT_DIR)

        #Nastavenie počiatočných parametrov pre predikciu neurónovou siete
        self.device = torch.device(config.TORCH_DEVICE if torch.cuda.is_available() else "cpu")
        if self.device.type == 'cuda':
            self.logs.logger.info(f"Initiasaion: Using {self.device.type} on {torch.cuda.get_device_name(torch.cuda.current_device())} as torch device")
        else:
            self.logs.logger.info(f"Initiasaion: Using {self.device.type} as torch device.")
        self._loadModel()

    #Otvorenie zvoleného modelu pomocou Pytorch a jeho príprava na predikciu
    def _loadModel(self):
        try:
            startTime = time.time()
            if config.TORCH_MODEL == "mobilenet_v3_large":
                self.model = models.mobilenet_v3_large(pretrained=False)
            elif config.TORCH_MODEL == "efficientnet_b7":
                self.model = models.efficientnet_b7(pretrained=False)
                self.model.classifier[1] = nn.Linear(in_features=2560, out_features=20)
            self.model.load_state_dict(torch.load(os.path.join(self.modelPath, f"{config.TORCH_MODEL}.pth")))
            self.model.eval()
            self.model.to(self.device)
            self.logs.logger.info(f"Initiasaion: Model {config.TORCH_MODEL.split('.')[0]} is loaded.")
            if config.LOG_TIME: self.logs.logger.info(f"Model initiasation took {time.time()-startTime:.4f}s")
        except:
            self.logs.logger.critical("Something when wrong during torch model initialisation")
            sys.exit(1)

    #Skontrolovanie výstupu z neurónovej siete po predikcií
    def _checkPrediction(self,output, fileName):
        #Úprava výstupu do percentuálnej formy a výber najväčšej hodnoty
        probs = nnf.softmax(output, dim=1)
        topProb, topClass = probs.topk(1, dim = 1)
        mappedClass = topClass.item()
        #Mapovanie výstupu na kategóriu
        if mappedClass < 16:
            mappedClass = config.REVERSE_CATEGORIES_MAP[mappedClass]

        #Odstránenie špecifického názvu útoku
        if not config.CATEGORIES_FULL_NAME: mappedClass = str(mappedClass).split(' ')[0]

        #Zaznamenanie výsledku do logov
        if int(topClass.item()) > 0:
            if fileName.split('.')[-1] == "csv":
                self.logs.logger.warning(f"{float(topProb.item()*100):.4f}% {fileName} CSV {mappedClass}")
            else:
                self.logs.logger.info(f"{float(topProb.item()*100):.4f}% {fileName} Image {mappedClass}")
        else:
            if fileName.split('.')[-1] == "csv":
                path = os.path.join(config.ROOT_DIRECTORY, config.CSV_DIRECTORY)
                utilities.csvRename(path, fileName, "FP"+fileName)      #Tagovanie súboru v prípade FP
                self.logs.logger.info(f"{float(topProb.item()*100):.4f}% {fileName} CSV {mappedClass} FalsePositive")
            else:
                self.logs.logger.info(f"{float(topProb.item()*100):.4f}% {fileName} Image {mappedClass} Negative")
        return (float(topProb.item()),mappedClass)

    #Vytvorenie predikcie na obrázkoch
    def getPrediction(self,image):
        startTime = time.time()
        try:
            with torch.no_grad():
                input = image[0].unsqueeze(0)
                input = input.to(self.device)
                outputs = self.model.forward(input)
                if config.LOG_TIME: self.logs.logger.info(f"Prediction took {time.time()-startTime:.4f}s")
                return self._checkPrediction(outputs, image[1])
        except:
            self.logs.logger.error("Something when wrong during predictions")


import os
import sys
import requests
from pymongo import MongoClient
from apscheduler.schedulers.blocking import BlockingScheduler

# For simplicity, we are hardcoding the GitHub URL here.
GITHUB_URL = "https://github.com/akto-api-security/pii-types/blob/master/general.json"
# We can also pass the GitHub URL as an environment variable or as a command line argument.
# GITHUB_URL = os.environ.get("GITHUB_URL") if os.environ.get("GITHUB_URL") else sys.argv[1] if len(sys.argv) > 1 else print("Please provide a GitHub URL.")

MONGO_URI = os.environ.get("MONGO_URI", "mongodb://localhost:27017")
MONGO_DB_NAME = os.environ.get("MONGO_DB_NAME", "pii_data")
MONGO_COLLECTION_NAME = os.environ.get("MONGO_COLLECTION_NAME", "patterns")
CRON_MINUTES = os.environ.get("CRON_MINUTES", 60)

client = MongoClient(MONGO_URI)
db = client[MONGO_DB_NAME]
collection = db[MONGO_COLLECTION_NAME]

def fetch_data(github_url: str):
    """
    Fetches data from the given GitHub URL and updates the MongoDB collection.
    """
    response = requests.get(github_url)
    if response.ok:
        data = response.json()
        if isinstance(data, dict):
            data = [data]
        collection.delete_many({})
        collection.insert_many(data)
        print("Data fetched and updated in MongoDB.")
    else:
        print("Error fetching data from GitHub.")

def trigger(minutes=int(CRON_MINUTES), github_file_link=GITHUB_URL):
    """
    Triggers the fetch_data function every given number of minutes (default: 60 minutes).
    """
    scheduler = BlockingScheduler()

    # For a single URL, you can use the following code:
    scheduler.add_job(fetch_data, 'interval', minutes=minutes, args=[github_file_link])

    # For multiple URLs, you can use the following code:
    # GITHUB_URLS = ["https://raw.githubusercontent.com/rabilrbl/akto/master/patterns.json", "https://raw.githubusercontent.com/rabilrbl/akto2/master/patterns.json"]
    # scheduler.add_job(fetch_data, 'interval', minutes=60, args=[GITHUB_URLS])

    scheduler.start()

if __name__ == "__main__":
    trigger()


from datetime import datetime, timedelta
import random
import re
import wsgiref.handlers
import cgi
import base64
from google.appengine.api import xmpp
from google.appengine.api import users
from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.ext.ereporter import report_generator
from google.appengine.ext.webapp import template
from google.appengine.ext.webapp import xmpp_handlers
from google.appengine.api.taskqueue import Task

from conversation import *
from stats import *

drinkers = set([])
informed = set([])
settingprefs = set([])
teacountdown = False
doublejeopardy = ""
lastround = datetime.now()

def send_random(recipient, choices, prefix=""):
  xmpp.send_message(recipient, prefix + random.sample(choices, 1)[0])

class Roster(db.Model):
  jid = db.StringProperty(required=True)
  teaprefs = db.StringProperty(required=False, default="")
  askme = db.BooleanProperty(required=False, default=True)
  newbie = db.BooleanProperty(required=False, default=True)

def get_roster():
  return Roster.all().fetch(limit=999)

def howTheyLikeItClause(message, talker):
  global TRIGGER_TEAPREFS
  global HOW_TO_TAKE_IT

  fromaddr = talker.jid

  # Try to work out if this includes a how they like it clause
  clauses = message.split(",")
  if len(clauses) > 1:
    del clauses[0]
    secondSentence = " ".join(clauses)
    if re.search(TRIGGER_TEAPREFS, secondSentence, re.IGNORECASE):
      talker.teaprefs = secondSentence
      talker.put()
      return

  # If they haven't given any preferences before
  if talker.teaprefs == "":
    send_random(fromaddr, HOW_TO_TAKE_IT)
    settingprefs.add(fromaddr)
    return

def selectByMadeVsDrunkRatio(drinkers):
  # Build a dictionary mapping the contents of drinkers onto their made drunk ratio
  probs = dict()
  probTotal = 0.0

  for person in drinkers:
    ratio = getMadeDrunkRatio(person)
    probs[person] = ratio
    probTotal += ratio

  # Now if we generate a random point between 0 and the total probs, then iterate through
  # the dictionary of listeners, summing the probabilities as we go, as soon as the probs
  # tip over the total, that person has been randomly selected with a weight.
  goalNumber = probTotal * random.random()
  probSum = 0.0
  for person in probs.keys():
    probSum += probs[person]
    if probSum >= goalNumber:
      return person

  # We probably had a floating point rounding error. Randomly select the first person in the list :P
  return drinkers[0]

def getSalutation(jid):
  return jid.split("@")[0].replace(".", " ").title()

def buildWellVolunteeredMessage(person):
  finalMessage = random.sample(WELL_VOLUNTEERED, 1)[0] + '\n'

  for drinker in drinkers:
    if drinker != person:
      temp = Roster.get_by_key_name(drinker)
      teapref = temp.teaprefs
      finalMessage += " * " + getSalutation(drinker) + " ("+teapref+")" + '\n'

  return finalMessage

class XmppHandler(xmpp_handlers.CommandHandler):
  """Handler class for all XMPP activity."""

  def unhandled_command(self, message=None):
    message.reply("I don't have any secret easter egg commands.....")

  def text_message(self, message=None):
    global NOBACKOUT
    global GREETING
    global AHGRAND
    global GOOD_IDEA
    global HUH
    global RUDE
    global NO_TEA_TODAY
    global JUST_MISSED
    global ADDPERSON
    global WANT_TEA

    global TRIGGER_HELLO
    global TRIGGER_YES
    global TRIGGER_TEA
    global TRIGGER_RUDE
    global TRIGGER_GOAWAY
    global TRIGGER_ADDPERSON
    global TRIGGER_TEAPREFS

    global teacountdown
    global drinkers
    global settingprefs
    global lastround

    fromaddr = self.request.get('from').split("/")[0]
    talker = Roster.get_or_insert(key_name=fromaddr, jid=fromaddr)

    # If they showed up in the middle of a round, ask them if they want tea in
    # the normal way after we've responded to this message
    if(talker.askme == False and teacountdown):
      send_random(fromaddr, WANT_TEA)
      informed.add(fromaddr)

    talker.askme=True
    talker.put()

    # Mrs Doyle takes no crap
    if re.search(base64.b64decode(TRIGGER_RUDE), message.body, re.IGNORECASE):
      send_random(fromaddr, RUDE)
      return

    # And sometimes people take no crap.
    if re.search(TRIGGER_GOAWAY, message.body, re.IGNORECASE):
      talker.askme=False
      talker.put()
      send_random(fromaddr, NO_TEA_TODAY)
      xmpp.send_presence(fromaddr, status=":( Leaving " + getSalutation(fromaddr) + " alone. So alone...", presence_type=xmpp.PRESENCE_TYPE_AVAILABLE)
      return

    xmpp.send_presence(fromaddr, status="", presence_type=xmpp.PRESENCE_TYPE_AVAILABLE)

    # See if we're expecting an answer as regards tea preferences
    if fromaddr in settingprefs:
      talker.teaprefs = message.body
      talker.put()
      settingprefs.remove(fromaddr)
      xmpp.send_message(fromaddr, "Okay!")
      return

    if teacountdown:
      if fromaddr in drinkers:
        if re.search(TRIGGER_TEAPREFS, message.body, re.IGNORECASE):
          xmpp.send_message(fromaddr, "So you like your tea '" + message.body + "'?")
          talker.teaprefs = message.body
          talker.put()
        elif re.search(TRIGGER_YES, message.body, re.IGNORECASE):
          xmpp.send_message(fromaddr, "Okay!")
        else:
          send_random(fromaddr, NOBACKOUT)
        return

      if re.search(TRIGGER_YES, message.body, re.IGNORECASE):
        drinkers.add(fromaddr)
        send_random(fromaddr, AHGRAND)
        howTheyLikeItClause(message.body, talker)

      else:
        send_random(fromaddr, AH_GO_ON)

    elif re.search(TRIGGER_ADDPERSON, message.body, re.IGNORECASE):
      emailtoinvite = re.search("("+TRIGGER_ADDPERSON+")", message.body, re.IGNORECASE).group(0)
      xmpp.send_invite(emailtoinvite)
      send_random(fromaddr, ADDPERSON)

    elif re.search(TRIGGER_TEA, message.body, re.IGNORECASE):
      send_random(fromaddr, GOOD_IDEA)
      howTheyLikeItClause(message.body, talker)

      drinkers.add(fromaddr)
      informed.add(fromaddr)

      for person in get_roster():
        if person.askme and not person.jid == fromaddr:
          xmpp.send_presence(jid=person.jid, presence_type = xmpp.PRESENCE_TYPE_PROBE)

      doittask = Task(countdown="120", url="/maketea")
      doittask.add()
      teacountdown = True

    elif re.search(TRIGGER_HELLO, message.body, re.IGNORECASE):
      send_random(fromaddr, GREETING)

    elif re.search(TRIGGER_YES, message.body, re.IGNORECASE) and (datetime.now() - lastround) < timedelta(seconds=120):
      send_random(fromaddr, JUST_MISSED)

    else:
      send_random(fromaddr, HUH)

class ProcessTeaRound(webapp.RequestHandler):
    def post(self):
      global ON_YOUR_OWN
      global WELL_VOLUNTEERED
      global OTHEROFFERED

      global drinkers
      global teacountdown
      global doublejeopardy
      global lastround
      global informed

      if len(drinkers) == 1:
        for n in drinkers:
          send_random(n, ON_YOUR_OWN)
      elif len(drinkers) > 0:
        # Select someone who wasn't the last person to make the tea
        doublejeopardy = teamaker = selectByMadeVsDrunkRatio(filter(lambda n : n != doublejeopardy, drinkers))

        for person in drinkers:
          if person == teamaker:
            statDrinker(person, len(drinkers))
            statRound  (person, len(drinkers))

            xmpp.send_message(person, buildWellVolunteeredMessage(person))
          else:
            send_random(person, OTHEROFFERED, getSalutation(teamaker))
            statDrinker(person)

      teacountdown = False
      drinkers = set([])
      settingprefs = set([])
      informed = set([])
      lastround = datetime.now()

class Register(webapp.RequestHandler):
    def post(self):
      global WANT_TEA
      global NEWBIE_GREETING
      global teacountdown
      global informed

      fromaddr = self.request.get('from').split("/")[0]
      person = Roster.get_or_insert(key_name=fromaddr, jid=fromaddr)

      if(person.newbie):
        xmpp.send_message(fromaddr, NEWBIE_GREETING)
        person.newbie = False
        person.put()

      if(not person.askme):
        xmpp.send_presence(fromaddr, status=":( Haven't heard from " + getSalutation(fromaddr) + " in a while...", presence_type=xmpp.PRESENCE_TYPE_AVAILABLE)
      else:
        xmpp.send_presence(fromaddr, status="", presence_type=xmpp.PRESENCE_TYPE_AVAILABLE)

      if(teacountdown and person.askme and fromaddr not in informed):
        send_random(fromaddr, WANT_TEA)
        informed.add(fromaddr)

def main():
  app = webapp.WSGIApplication([
      ('/maketea', ProcessTeaRound),
      ('/_ah/xmpp/message/chat/', XmppHandler),
      ('/_ah/xmpp/presence/available/', Register),
      ('/_ah/xmpp/subscription/subscribe/', Register),
      ], debug=True)
  wsgiref.handlers.CGIHandler().run(app)

if __name__ == '__main__':
  main()


#!/usr/bin/env python3
from enum import Enum
from copy import deepcopy
import sys
import readline # make input() use readline <3

CODE_BASE_ADDR = 0x1000 # where code is loaded
MEMORY_SIZE = 0xFFFF # How big memory is

INSTR_PTR_LOC = 0x0
STACK_PTR_LOC = 0x1
BASE_PTR_LOC = 0x2

class InstructionFamily(Enum):
    IMM = 0
    ARTIH = 1
    JMP = 2

class Opcode(Enum):
    MI = 0x0
    MV = 0x1
    MD = 0x2
    LD = 0x3
    ST = 0x4
    AD = 0x5
    SB = 0x6
    ND = 0x7
    OR = 0x8
    XR = 0x9
    SR = 0xa
    SL = 0xb
    SA = 0xc
    JG = 0xd
    JL = 0xe
    JQ = 0xf

class Instruction:
    def __init__(self, address, opcode, *args):
        self.address = address
        self.opcode = Opcode(opcode)
        if self.opcode in [Opcode.MI]:
            self.family = InstructionFamily.IMM
            self.rm = args[0]
            self.imm = args[1]
            self.length = 2 # num words
        elif self.opcode in [Opcode.MV, Opcode.MD, Opcode.LD, Opcode.ST, \
                             Opcode.AD, Opcode.SB, Opcode.ND, Opcode.OR, \
                             Opcode.XR, Opcode.SR, Opcode.SL, Opcode.SA]:
            self.family = InstructionFamily.ARTIH
            self.rm = args[0]
            self.mem = args[1]
            self.length = 2 # num words
        elif self.opcode in [Opcode.JG, Opcode.JL, Opcode.JQ]:
            self.family = InstructionFamily.JMP
            self.rm = args[0]
            self.mem = args[1]
            self.label = args[2]
            self.length = 3 # num words
        else:
            raise NotImplemented('WHAT EVEN THATS NOT A POSSIBLE OPCODE: {:x}'.format(self.opcode))

    def mnemonic(self):
        mnemonics = {
            Opcode.MI: 'MI',
            Opcode.MV: 'MV',
            Opcode.MD: 'MD',
            Opcode.LD: 'LD',
            Opcode.ST: 'ST',
            Opcode.AD: 'AD',
            Opcode.SB: 'SB',
            Opcode.ND: 'ND',
            Opcode.OR: 'OR',
            Opcode.XR: 'XR',
            Opcode.SR: 'SR',
            Opcode.SL: 'SL',
            Opcode.SA: 'SA',
            Opcode.JG: 'JG',
            Opcode.JL: 'JL',
            Opcode.JQ: 'JQ'
        }
        mnemonic = mnemonics[self.opcode]
        if mnemonic == 'JQ' and self.rm == 0x0 and self.mem == 0x0 and self.address == self.label:
            mnemonic = 'HF' # special case for halt-and-catch-fire, which is a specialization of jmp to self
        return mnemonic

    def __repr__(self):
        if self.family == InstructionFamily.IMM:
            return 'Instruction(0x{:x}, 0x{:x}, [0x{:x}, 0x{:x}])'.format( \
                    self.address, self.opcode, \
                    self.rm, self.imm)
        elif self.family == InstructionFamily.ARTIH:
            return 'Instruction(0x{:x}, 0x{:x}, [0x{:x}, 0x{:x}])'.format( \
                    self.address, self.opcode, \
                    self.rm, self.mem)
        elif self.family == InstructionFamily.JMP:
            return 'Instruction(0x{:x}, 0x{:x}, [0x{:x}, 0x{:x}, 0x{:x}])'.format( \
                    self.address, self.opcode, \
                    self.rm, self.mem, self.label)
        else:
            raise NotImplemented('What even instruction did you give me')

    def __str__(self):
        """
        If you want a prettier version, use .nice_str
        """
        if self.family == InstructionFamily.IMM:
            return '{} 0x{:x}, 0x{:x}'.format(self.mnemonic(), self.rm, self.imm)
        elif self.family == InstructionFamily.ARTIH:
            return '{} 0x{:x}, 0x{:x}'.format(self.mnemonic(), self.rm, self.mem)
        elif self.family == InstructionFamily.JMP:
            if self.opcode == Opcode.JQ and self.rm == 0x0 and self.mem == 0x0 and self.address == self.label:
                return 'HF' # special case for halt-and-catch-fire
            return '{} 0x{:x}, 0x{:x}, 0x{:x}'.format(self.mnemonic(), self.rm, self.mem, self.label)

    def nice_str(self, symbol_fn):
        """
        symbol_fn takes an address and returns a pretty string for it.
        """
        if self.family == InstructionFamily.IMM:
            return '{} {}, {}'.format(self.mnemonic(), symbol_fn(self.rm), symbol_fn(self.imm))
        elif self.family == InstructionFamily.ARTIH:
            return '{} {}, {}'.format(self.mnemonic(), symbol_fn(self.rm), symbol_fn(self.mem))
        elif self.family == InstructionFamily.JMP:
            if self.opcode == Opcode.JQ and self.rm == 0x0 and self.mem == 0x0 and self.address == self.label:
                return 'HF' # special case for halt-and-catch-fire
            return '{} {}, {}, {}'.format(self.mnemonic(), symbol_fn(self.rm), symbol_fn(self.mem), symbol_fn(self.label))

    @classmethod
    def decode(cls, address, instruction_memory):
        opcode = (instruction_memory[0] >> 12) & 0xF
        args = []
        args.append(instruction_memory[0] & 0xFFF)
        args.append(instruction_memory[1])
        args.append(instruction_memory[2])
        return cls(address, opcode, *args)

class InvalidAccess(Exception):
    """
    Quick helper for invalid accesses
    """
    pass

def invalid_access(state, addr, *args):
    """
    Interrupt to represent things as invalid
    """
    raise InvalidAccess('Cannot read/write memory at addr {:x}'.format(addr))

def getchar_interrupt(state, addr, *args):
    """
    getchar interrupt
    """
    return state.get_input()

def putchar_interrupt(state, addr, *args):
    """
    putchar interrupt
    """
    char = args[0]
    state.add_output(chr(char))

class BreakpointHit(Exception):
    """
    Raise when a bp is hit
    """
    pass

class Memory:
    def __init__(self, memory):
        self.memory = memory
        self.state = None # fill this in l8r

    def __getitem__(self, key):
        if not isinstance(key, slice) and \
                key in self.state.read_interrupts:
            return self.state.read_interrupts[key](self.state, key)
        return self.memory[key]

    def __setitem__(self, key, value):
        if key in self.state.write_interrupts:
            self.state.write_interrupts[key](self.state, key, value)
            return
        self.memory[key] = value

class State:
    def __init__(self, memory):
        self.memory = memory
        self.halt = False # is cpu halted
        # addr read -> function to call for value to be read
        self.read_interrupts = {
            0x200: invalid_access, # reading output register
            0x201: getchar_interrupt, # getchar
            0x202: invalid_access,  # gen key write
            0x203: None,  # gen key read
            0x204: invalid_access,  # load key write
            0x205: None,  # load key read
            0x206: invalid_access,  # mul write
            0x207: None,  # mul read
            0x208: None,  # rng
            0x209: invalid_access,  # set timeout
            0x20A: None,  # timeout addr
        }
        # addr written -> function to call with writen value
        self.write_interrupts = {
            0x200: putchar_interrupt, # putchar
            0x201: invalid_access, # writing input register
            0x202: None,  # gen key write
            0x203: invalid_access,  # gen key read
            0x204: None,  # load key write
            0x205: invalid_access,  # load key read
            0x206: None,  # mul write
            0x207: invalid_access,  # mul read
            0x208: invalid_access,  # rng
            0x209: None,  # set timeout
            0x20A: None,  # timeout addr
        }
        self.all_input = ''
        self.all_output = ''

    def decode_instruction_at(self, addr):
        instr_memory = self.memory[addr : addr + 3] # we need 3 words for jmp family instructions
        return Instruction.decode(addr, instr_memory)

    def instruction_pointer(self):
        return self.memory[INSTR_PTR_LOC]

    def stack_pointer(self):
        return self.memory[STACK_PTR_LOC]

    def step(self):
        ip = self.instruction_pointer()
        instr = self.decode_instruction_at(ip)
        next_ip = self.instruction_pointer() + instr.length
        self.memory[INSTR_PTR_LOC] = next_ip # advance ip
        if instr.opcode == Opcode.MI:
            self.memory[instr.rm] = instr.imm
        elif instr.opcode == Opcode.MV:
            self.memory[instr.rm] = self.memory[instr.mem]
        elif instr.opcode == Opcode.MD:
            self.memory[instr.rm] = self.memory[self.memory[instr.mem]]
        elif instr.opcode == Opcode.LD:
            self.memory[self.memory[instr.rm]] = self.memory[instr.mem]
        elif instr.opcode == Opcode.ST:
            self.memory[self.memory[instr.mem]] = self.memory[instr.rm]
        elif instr.opcode == Opcode.AD:
            self.memory[instr.rm] += self.memory[instr.mem]
        elif instr.opcode == Opcode.SB:
            self.memory[instr.rm] -= self.memory[instr.mem]
        elif instr.opcode == Opcode.ND:
            self.memory[instr.rm] &= self.memory[instr.mem]
        elif instr.opcode == Opcode.OR:
            self.memory[instr.rm] |= self.memory[instr.mem]
        elif instr.opcode == Opcode.XR:
            self.memory[instr.rm] ^= self.memory[instr.mem]
        elif instr.opcode == Opcode.SR:
            self.memory[instr.rm] >>= self.memory[instr.mem]
        elif instr.opcode == Opcode.SL:
            self.memory[instr.rm] <<= self.memory[instr.mem]
        elif instr.opcode == Opcode.SA:
            rm = self.memory[instr.rm]
            mem = self.memory[instr.mem]
            to_signed = lambda x: int.from_bytes(x.to_bytes(2, 'little'), 'little', True)
            to_unsigned = lambda x: int.from_bytes(x.to_bytes(2, 'little', True), 'little')
            rm = to_signed(rm)
            res = rm >> mem
            memory[rm] = to_unsigned(rm)
        elif instr.opcode == Opcode.JG:
            if self.memory[instr.rm] > self.memory[instr.mem]:
                self.memory[INSTR_PTR_LOC] = instr.label
        elif instr.opcode == Opcode.JL:
            if self.memory[instr.rm] < self.memory[instr.mem]:
                self.memory[INSTR_PTR_LOC] = instr.label
        elif instr.opcode == Opcode.JQ:
            if instr.rm == 0 and instr.mem == 0 and instr.label == ip:
                # halt-and-catch-fire
                self.cpu_abort()
            elif self.memory[instr.rm] == self.memory[instr.mem]:
                self.memory[INSTR_PTR_LOC] = instr.label

    def run(self):
        while not self.halt:
            instr = self.decode_instruction_at(self.instruction_pointer())
            self.step()

    def cpu_abort(self):
        self.halt = True

    def get_input(self):
        char = sys.stdin.read(1)
        self.all_input += char
        return ord(char)

    def add_output(self, value):
        self.all_output += value
        sys.stdout.write(value)
        sys.stdout.flush()

    @classmethod
    def from_file(cls, filename):
        with open(filename, 'rb') as f:
            contents = f.read()

        # reinterpret contents as an array of little-endian words
        code = []
        for i in range(0, len(contents), 2):
            mem = contents[i : i+2]
            word = int.from_bytes(mem, 'little')
            code.append(word)

        # memory space, all 0's to start
        memory = [0 for _ in range(MEMORY_SIZE+1)]

        # initialize stack ptr
        memory[STACK_PTR_LOC] = 0x300
        # initialize instruction ptr
        memory[INSTR_PTR_LOC] = 0x1000

        # initialize code
        for offset, word in enumerate(code):
            memory[CODE_BASE_ADDR + offset] = word

        st = State(Memory(memory))
        st.memory.state = st
        return st

class InteractiveDebugger:
    """
    An interactive debugger.

    Commands:
    again: repeat last command (just hitting return also does this)
    r: run, start the program if not running
    s, ni, n: single step.
        - Takes argument of number of instructions to step (default 1)
    c: run until breakpoint.
    b: break at address.
        - for example, b 0x1234 creates a breakpoint at the instruction 0x1234
    p: print expression. Takes a format specifier, defaulting to "word"
        - Format specifiers come in the form p/NX,
          where N is a number of consecutive version of the argument type to print,
          and X is the specifier.
          supports the following specifiers:
            w, x: word
            c: character
            i: instruction
    load-labels: Load labels from a file (argument required)

    Expressions: **TODO**
    right now just hex numbers for an addr haha
    """
    def __init__(self, state):
        self.initial_state = state
        self.state = None
        self.running = False

        self.symbols = {
            INSTR_PTR_LOC: 'IP',
            STACK_PTR_LOC: 'SP',
            0x3: 'SC0',
            0x4: 'SC1',
            0x200: 'OUTPUT',
            0x201: 'INPUT',
        }
        # rNN registers
        for i, x in enumerate(range(0x10, 0x40)):
            name = 'r{:02x}'.format(i)
            self.symbols[x] = name

        # sNN registers
        for i, x in enumerate(range(0x40, 0x100)):
            name = 's{:02x}'.format(i)
            self.symbols[x] = name

        self.breakpoints = {} # bp number -> bp addr
        self.highest_breakpoint_num = 0 # breakpoint #s monotonically increment

        self.last_command = ''

        # commands to run always
        self.autocmds = [
            #'p/10i *$ip', # instruction leadup
        ]

        self.watch_addrs = [INSTR_PTR_LOC, STACK_PTR_LOC]

    def input_loop(self):
        while True:
            self.print_banner()
            self.handle_input()

    def handle_input(self):
        """
        Handle getting input, parsing it, executing commands
        """
        inp = input('ctdbg> ')
        self.runcmd(inp)

    def runcmd(self, cmd, user=True):
        def print_stuff(num, specifier, loc):
            specifiers = {
                'x': lambda addr: hex(self.state.memory[addr]),
                'c': lambda addr: bytes([self.state.memory[addr]]),
                'i': lambda addr: hex(addr) + ': ' + self.state.decode_instruction_at(addr).nice_str(self.nice_format_addr),
            }
            specifiers['w'] = specifiers['x'] # w = x

            joinchrs = {
                'x': ' ',
                'c': b'',
                'i': '\n',
            }
            joinchrs['w'] = joinchrs['x'] # ditto to above

            try:
                fmt_fn = specifiers[specifier]
                # gather all outs into this nice lil array
                outs = []
                offset = 0
                for i in range(num):
                    addr = loc + offset
                    if specifier != 'i':
                        offset += 1
                    else:
                        offset += self.state.decode_instruction_at(addr).length
                    outs.append((addr, fmt_fn(addr)))
                #TODO: decide ways of formatting based on length
                out = joinchrs[specifier] + joinchrs[specifier].join(y for x,y in outs)
                if specifier == 'c': # get a repr for non-printables :)
                    out = repr(out)
                return out
            except Exception as e:
                print("err> Error formatting:", e)

        def step(n):
            if not self.running:
                self.restart()

            if self.state.halt:
                print('err> STATE HALTED, USE r TO RESTART')
                return

            for _ in range(n):
                self.state.step()

        def run():
            self.restart()
            self.run()

        def add_watch(addr):
            self.watch_addrs.append(addr)
            return 'Added a watchpoint for {:x}'.format(addr)

        cmd_dict = {
            'again': lambda: self.runcmd(self.last_command),
            'r': lambda: run(), # different then self.run
            's': lambda n: step(n),
            'c': lambda: self.run(),
            'b': lambda target: self.add_breakpoint(target),
            'p': lambda num, specifier, loc: print_stuff(num, specifier, loc),
            'd': lambda num: self.del_breakpoint(num),
            'watch': add_watch,
            'load-labels': lambda filename: self.load_symbols_from_file(filename),
        }

        try:
            parsed = self.parse_command(cmd)
            cmd_fn = cmd_dict[parsed[0]]
            args = parsed[1:]
            res = cmd_fn(*args)

            if res:
                print('out>', res)

            # only set last_command if user-initiated and not a repeat command
            if user and cmd not in {'again', ''}:
                self.last_command = cmd
        except ValueError as e:
            print('err> Unknown command:', cmd)
            print(e)

    def run(self):
        if not self.running:
            self.restart()

        try:
            while not self.state.halt:
                self.state.step()
                # bp check
                if self.state.instruction_pointer() in self.breakpoints.values():
                    raise BreakpointHit()
        except BreakpointHit:
            print('out> breakpoint hit!!')
        except KeyboardInterrupt:
            print('out> paused!!')

        if self.state.halt:
            self.running = False
            print('out> program hlt!!')

    def nice_format_addr(self, addr):
        """
        Format an address in the nicest way we can figure out
        """
        if addr in self.symbols:
            return '{} (0x{:x})'.format(self.symbols[addr], addr)

        return hex(addr)

    def print_banner(self):
        """
        Print a banner of watched variables, plus some other info
        (if cpu is halted, upcoming instructions, etc)
        """
        print('<===================')
        if self.state:
            for addr in self.watch_addrs:
                nice_addr, val = self.nice_format_addr(addr), self.state.memory[addr]
                print('{} = {:x}'.format(nice_addr, val))

            if self.state.halt:
                print('CPU HALTED')

            print('\n====================\n')

            for cmd in self.autocmds:
                try:
                    self.runcmd(cmd, user=False)
                except Exception as e:
                    pass
        else:
            print('Not running!')
        print('===================>')

    def parse_command(self, cmd):
        operation = cmd.split(' ')[0]
        arg = ' '.join(cmd.split(' ')[1:])
        if operation == '' or operation == 'again':
            return ('again',) # do last command again
        elif operation == 'r':
            return ('r',)
        elif operation in {'s', 'ni', 'n'}:
            if arg:
                arg = self.parse_expression(arg)
            else:
                arg = 1
            return ('s', 1)
        elif operation == 'c':
            return ('c',)
        elif operation == 'b':
            if not arg:
                raise ValueError('Argument required for b command')
            return ('b', self.parse_expression(arg))
        elif operation == 'watch':
            if not arg:
                raise ValueError('Argument required for watch command')
            return ('watch', self.parse_expression(arg))
        elif operation[0] in {'p', 'x'}:
            if '/' in operation:
                p_len = operation[operation.index('/') + 1 : -1]
                if p_len:
                    p_len = int(p_len)
                else:
                    p_len = 1
                p_specifier = operation[-1]
            else:
                p_len = 1
                p_specifier = 'w'
            return ('p', p_len, p_specifier, self.parse_expression(arg))
        elif operation == 'load-labels':
            if arg:
                return ('load-labels', arg)
            raise ValueError('Argument required for load-labels command')
        else:
            raise ValueError('Unknown command!')

    def parse_expression(self, expression):
        #TODO: * for deref, + and -
        # TODO: better gooder parsing?
        expression = expression.lower()
        for addr, name in self.symbols.items():
            expression = expression.replace('$' + name.lower(), hex(addr))
        needs_deref = False
        if expression[0] == '*':
            expression = expression[1:]
            needs_deref = True
        val = int(expression, 16)
        if needs_deref:
            val = self.state.memory[val]
        return val

    def add_breakpoint(self, addr):
        max_bp = self.highest_breakpoint_num
        bp_num = max_bp + 1
        self.breakpoints[bp_num] = addr
        self.highest_breakpoint_num += 1
        return bp_num

    def del_breakpoint(self, num):
        self.breakpoints.pop(num)

    def restart(self):
        """
        Restart the program under test (basically the `r` command)
        """
        self.state = deepcopy(self.initial_state)
        self.running = True

    def load_symbols_from_file(self, filename):
        new_symbols = {}
        with open(filename, 'r') as f:
            for line in f:
                if line.strip():
                    addr, name = line.split(':')
                    addr = int(addr, 0)
                    name = name.strip()
                    new_symbols[addr] = name
        print('Loaded {} symbols'.format(len(new_symbols)))
        self.symbols.update(new_symbols)

def main(argv):
    if len(argv) != 2:
        print('ERROR: ROM argument required!')
        return

    st = State.from_file(argv[1])
    dbg = InteractiveDebugger(st)
    dbg.runcmd('load-labels symbols')
    dbg.input_loop()

if __name__ == '__main__':
    main(sys.argv)