#!/usr/bin/env python3
# 
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#


import ctypes
import queue

from qiling.core import Qiling
from qiling.hw.peripheral import QlPeripheral


class PeripheralTube(queue.Queue):
    def __init__(self):
        super().__init__()

    def readable(self) -> bool:
        return not self.empty()

    def read(self, numb:int = 4096) -> bytes:
        data = bytearray()
        for _ in range(numb):
            if not self.readable():
                break
            data.append(self.get())

        return bytes(data)

    def write(self, data: bytes):
        for byte in bytearray(data):
            self.put(byte)

    def save(self):
        data = bytearray()        
        while self.readable():
            data.append(self.get())
        
        self.write(data)
        return bytes(data)

    def restore(self, data: bytes):
        while self.readable():
            self.get()
        
        self.write(data)


class QlConnectivityPeripheral(QlPeripheral):
    class Type(ctypes.Structure):
        """ Define the reigister fields of peripheral.

            Example:
                fields_ = [
                    ('SR'  , ctypes.c_uint32),
                    ('DR'  , ctypes.c_uint32),
                    ('BRR' , ctypes.c_uint32),
                    ('CR1' , ctypes.c_uint32),
                    ('CR2' , ctypes.c_uint32),
                    ('CR3' , ctypes.c_uint32),
                    ('GTPR', ctypes.c_uint32),
                ]
        """        
        _fields_ = []

    def __init__(self, ql: Qiling, label: str, limit:int = 1):
        super().__init__(ql, label)
        
        self.itube = PeripheralTube()
        self.otube = PeripheralTube()

        self.limit = limit
        self.device_list = []

    def has_input(self):
        return self.itube.readable()

    def send(self, data: bytes):
        """ Send data into the peripheral.
            
            Example:
                ql.hw.usart1.send(b'hello')
        """
        self.itube.write(data)        

    def recv(self, numb:int = 4096) -> bytes:
        """ Receive data from peripheral

            Example:
                data = ql.hw.i2c1.recv()
        """
        return self.otube.read(numb)

    def send_to_user(self, data: int):
        """ send single byte to user
        """        
        self.otube.put(data)

    def recv_from_user(self) -> bytes:
        """ Read single byte from user input
        """        
        return self.itube.get()

    def connect(self, device):
        if len(self.device_list) < self.limit:
            self.device_list.append(device)

    @staticmethod
    def device_handler(func):
        """ Send one byte to all devices
        """
        def wrapper(self):            
            if len(self.device_list) > 0:                
                if self.otube.readable():
                    data = self.recv(1)
                    for device in self.device_list:
                        device.send(data)

                for device in self.device_list:
                    device.step()

            func(self)
        
        return wrapper

    def save(self):
        return self.itube.save(), self.otube.save(), bytes(self.instance)

    def restore(self, data):
        itube, otube, instance = data
        
        self.itube.restore(itube)
        self.otube.restore(otube)
        ctypes.memmove(ctypes.addressof(self.instance), instance, len(data))
