from saleae.analyzers import AnalyzerFrame
from SecondLayerHLAs.SecondLayerAnalyzerHandler import SecondLayerAnalyzerHandler
from I3CDictionaries import I3CDictionaries, DecodingModes, DDRErrorNames
from GenericDecoder import GenericDecoder
import logging
import sys

def convertStringToInt(string: str) -> int:
    try:
        return int(string, 16)
    except ValueError:
        return 0

# DDR Decoder must subclass the Generic Decoder class.
class DDRDecoder(GenericDecoder):

    I3C_logger = logging.getLogger("I3C")

    ccc_dict = {}
    result_types = I3CDictionaries.result_types

    temp_frame = None
    direct_ccc = False

    def __init__(self, second_layer_HLA_option, i3c_version_option):
        """
        Initialize HLA.

        Settings can be accessed using the same name used above.
        """
        logging.basicConfig(level=logging.INFO, stream=sys.stdout)

        """
        Obtain an instance of the second layer handler and call the method get to
        obtain an instance of the second layer hla that the user selected for SDR
        """
        handler = SecondLayerAnalyzerHandler()
        self.ccc_dict = I3CDictionaries.getCCCDict(i3c_version_option)
        self.second_layer_analyzer = handler.get(second_layer_HLA_option, DecodingModes.DDR)
        self.second_layer_analyzer.update_result_types(self.result_types)
        self.second_layer_analyzer.update_ccc(self.ccc_dict)

    def processFrame(self, frame: AnalyzerFrame):

        # Received a HDR type frame
        if frame.type == "DDR HDR R-START" or frame.type == "DDR HDR EXIT":
            # If a direct CCC is processed, then the HLA frame is not completed yet
            if frame.type == "DDR HDR R-START" and self.direct_ccc:
                self.direct_ccc = False
                return None, DecodingModes.DDR

            # clear temporary frame
            self.temp_frame = None
            # Push the single HDR pattern HLA frame
            new_frame = AnalyzerFrame(
                "I3C HDR Pattern",
                frame.start_time,
                frame.end_time,
                {"Info": frame.data["DDR Info"], "Length": 0},
            )
            s = self.result_types[new_frame.type]["console"]
            self.I3C_logger.info(s.format(**new_frame.data))

            new_frame.data["Length"] = str(new_frame.data["Length"])

            # If an HDR exit is received then DDR mode must be exited
            if "HDR Exit" in frame.data["DDR Info"]:
                return self.second_layer_analyzer.instruction_decoder(new_frame), DecodingModes.SDR
            # If an HDR restart is received then DDR mode is maintained
            else:
                return self.second_layer_analyzer.instruction_decoder(new_frame),DecodingModes.DDR

        #Received a DDR HEADER type frame
        elif frame.type == "DDR CMD":

            address_string = "0x" + frame.data["DDR Address"].hex()
            command_string = "0x" + frame.data["DDR Command"].hex()
            validity = ""
            #Append error to data structure
            validity += frame.data["DDR Error"]
            
            # Set up Write frame
            if frame.data["DDR W/R"] == "W":
                self.temp_frame = AnalyzerFrame(
                    "DDR Write to address",
                    frame.start_time,
                    frame.end_time,
                    {
                        "Data": "",
                        "Length": 0,
                        "Address": address_string,
                        "Command": command_string,
                        "Error": validity,
                    },
                )
            # Set up Read frame
            elif frame.data["DDR W/R"] == "R":
                self.temp_frame = AnalyzerFrame(
                    "DDR Read from address",
                    frame.start_time,
                    frame.end_time,
                    {
                        "Data": "",
                        "Length": 0,
                        "Address": address_string,
                        "Command": command_string,
                        "Error": validity,
                    },
                )

        # Received a CRC type frame
        elif frame.type == "DDR CRC":
            # If a direct CCC was received, then the HLA frame is not completed yet.
            # Otherwise, the frame is completed and can be returned.
            if self.direct_ccc:
                return None, DecodingModes.DDR
            
            # Store data of temporary frame and clear
            new_frame = self.temp_frame
            self.temp_frame = None

            #Update the ending time o the frame
            new_frame.end_time = frame.end_time

            #Append error to data structure
            if(new_frame.data["Error"] != ""):
                new_frame.data["Error"] += "-"
            new_frame.data["Error"] += frame.data["DDR Error"]

            #Check if the frame contains all the needed information
            s = self.result_types[new_frame.type]["console"]
            try:
                # This will generate an error if there is any incomplete data in the frame (for example, an BC header without address)
                self.I3C_logger.info(s.format(**new_frame.data))

            except:
                new_frame = AnalyzerFrame(
                    "Incomplete Transaction",
                    new_frame.start_time,
                    new_frame.end_time,
                    {
                        "Data": new_frame.data["Data"],
                        "Length": new_frame.data["Length"],
                    },
                )

                s = self.result_types[new_frame.type]["console"]
                self.I3C_logger.info(s.format(**new_frame.data))

            new_frame.data["Length"] = str(new_frame.data["Length"])

            return self.second_layer_analyzer.instruction_decoder(new_frame), DecodingModes.DDR

        # Received a DATA type frame
        elif frame.type == "DDR DATA":

            # If the frame is already set up
            if self.temp_frame != None:
                # Update the length field
                self.temp_frame.data["Length"] += 1

                # Update the data field
                if len(self.temp_frame.data["Data"]) > 0:
                    self.temp_frame.data["Data"] += ", "
                self.temp_frame.data["Data"] += frame.data["DDR Data"]

                #Append error to data structure if not already added
                if(self.temp_frame.data["Error"] == ""):
                    self.temp_frame.data["Error"] += frame.data["DDR Error"]
                    

        elif frame.type == "DDR ABORT":
            if self.temp_frame != None:
                # Store data of temporary frame and clear
                new_frame = self.temp_frame
                self.temp_frame = None

                new_frame.end_time = frame.end_time

                if new_frame.type == "DDR Write to address":
                    new_frame.type = "Aborted Write DDR"
                elif new_frame.type == "DDR Read from address":
                    new_frame.type = "Aborted Read DDR"

                s = self.result_types[new_frame.type]["console"]
                
                try:
                    # This will generate an error if there is any incomplete data in the frame (for example, an BC header without address)
                    self.I3C_logger.info(s.format(**new_frame.data))

                except:
                    new_frame = AnalyzerFrame(
                        "Incomplete Transaction",
                        new_frame.start_time,
                        new_frame.end_time,
                        {
                            "Data": new_frame.data["Data"],
                            "Length": new_frame.data["Length"],
                        },
                    )
                    s = self.result_types[new_frame.type]["console"]

                    self.I3C_logger.info(s.format(**new_frame.data))
                
                finally:
                    new_frame.data["Length"] = str(new_frame.data["Length"])

            else:

                new_frame = AnalyzerFrame(
                    "DDR Abort",
                    frame.start_time,
                    frame.end_time
                )
                s = self.result_types[new_frame.type]["console"]
                self.I3C_logger.info(s.format(**new_frame.data))

            return self.second_layer_analyzer.instruction_decoder(new_frame), DecodingModes.DDR


        elif frame.type == "DDR ERROR":

            if self.temp_frame != None:
                # Store data of temporary frame and clear
                new_frame = self.temp_frame
                self.temp_frame = None
                new_frame.end_time = frame.end_time

                if(new_frame.data["Error"]):
                    new_frame.data["Error"] += "-"

                new_frame.data["Error"] += DDRErrorNames.UNKNOWN_ERROR

                s = self.result_types[new_frame.type]["console"]

                try:
                    # This will generate an error if there is any incomplete data in the frame (for example, an BC header without address)
                    self.I3C_logger.info(s.format(**new_frame.data))

                except:
                    new_frame = AnalyzerFrame(
                        "Incomplete Transaction",
                        new_frame.start_time,
                        new_frame.end_time,
                        {
                            "Data": new_frame.data["Data"],
                            "Length": new_frame.data["Length"],
                        },
                    )

                    s = self.result_types[new_frame.type]["console"]
                    self.I3C_logger.info(s.format(**new_frame.data))

                finally:
                    new_frame.data["Length"] = str(new_frame.data["Length"])
                
                return self.second_layer_analyzer.instruction_decoder(new_frame), DecodingModes.DDR
            else:

                self.temp_frame = None
                new_frame = AnalyzerFrame(
                    "DDR Unknown Transaction",
                    frame.start_time,
                    frame.end_time
                )

                return new_frame, DecodingModes.DDR
            
        # CCC indicator
        elif frame.type == "DDR CCC IND":
            self.direct_ccc = False
            # Create a new frame, defaulting as a Message frame
            self.temp_frame = AnalyzerFrame(
                    "Message",
                    frame.start_time,
                    frame.end_time,
                    {
                        "Data": "",
                        "Length": 0,
                        "Error": "",
                    },
                )
        
        # CCC Command
        elif frame.type == "DDR CCC CMD":
            # If the frame is already set up
            if self.temp_frame is not None:
                ccc_cmd = (convertStringToInt(frame.data["DDR Data"]) & 0xFF00) >> 8
                def_byte = convertStringToInt(frame.data["DDR Data"]) & 0x00FF

                if ccc_cmd < 0x80:
                    self.temp_frame.type = "DDR BC CCC Transaction"
                else:
                    self.temp_frame.type = "DDR Direct CCC Transaction"
                    self.direct_ccc = True

                ccc_name = self.ccc_dict.get(ccc_cmd, "Unknown CCC")
                self.temp_frame.data["Info"] = ccc_name
                self.temp_frame.data["DefByte"] = hex(def_byte).upper().replace("X","x")

                # Update the length field
                self.temp_frame.data["Length"] += 1

                # Update the data field
                if len(self.temp_frame.data["Data"]) > 0:
                    self.temp_frame.data["Data"] += ", "
                self.temp_frame.data["Data"] += frame.data["DDR Data"]
        
        # CCC Data
        elif frame.type == "DDR CCC DATA":
            # If the frame is already set up
            if self.temp_frame is not None:
                # Update the length field
                self.temp_frame.data["Length"] += 1

                # Update the data field
                if len(self.temp_frame.data["Data"]) > 0:
                    self.temp_frame.data["Data"] += ", "
                self.temp_frame.data["Data"] += frame.data["DDR Data"]

        # CCC Selector
        elif frame.type == "DDR CCC SEL":
            # If the frame is already set up
            if self.temp_frame is not None:
                # Update the length field
                self.temp_frame.data["Length"] += 1

                # Update the data field
                if len(self.temp_frame.data["Data"]) > 0:
                    self.temp_frame.data["Data"] += ", "
                self.temp_frame.data["Data"] += "0x" + frame.data["DDR Address"].hex() + frame.data["DDR Command"].hex()

                self.temp_frame.data["Address"] = "0x" + frame.data["DDR Address"].hex()

        # Exit DDR Mode
        elif frame.type == "DDR STOP":
            return None, DecodingModes.SDR
        elif frame.type == "UNSUPPORTED":
            self.temp_frame = None
            new_frame = AnalyzerFrame(
                "UNSUPPORTED",
                frame.start_time,
                frame.end_time
            )
            return new_frame, DecodingModes.DDR
        else:
            new_frame = self.temp_frame
            self.temp_frame = None
            new_frame.end_time = frame.end_time

            new_frame = AnalyzerFrame(
                "DDR Unknown Transaction",
                new_frame.start_time,
                new_frame.end_time
            )

            return new_frame, DecodingModes.DDR


        return None, DecodingModes.DDR

    def CheckCRC(self, message):
        ret = "ERROR"
        if "OK" in message:
            ret = "OK"
        return ret
