from datetime import date
from re import compile
from uuid import uuid4, UUID

from bitarray import bitarray
from bitarray.util import int2ba, ba2int

'''
        ------------------------------------------
        BIT LAYOUT FOR MAPPED EPOC + IDENTIFIER ID
        ------------------------------------------

        Bits 0-26 = Date
        Date is encoded as a 27-bit integer to represent an 8-digit decimal number
        to make it easy to translate from an ISO date (2025-01-01 becomes 20250101)
        This is quite an inefficient encoding, and can be optimised e.g. to use 
        integral days since 1970-01-01, if more space is needed.

        Bits 27-41 = Date sequence
        Encoded as a 14 bit integer representing a four digit decimal number as per 
        the EPOC GCI definition.

        Bits 42-45 = Instrument type
        Encoded as a 4 bit integer representing the index of the instrument type
        in an enum. If we assume this is mapping is only needed for EPOC/EPOC-PR,
        we can trim this down to a single bit if needed (EPOC = 0, EPOC-PR = 1)

        Bits 46-59 = Issuing country
        Country code encoded as a 10-bit integer, representing an ISO country code
        from "AA" to "ZZ" ("AA" = 0, "AB" = 1 ... "ZZ" = 675). This allows all
        ISO country codes to be carried. If it is OK to only carry those
        codes representing EU Member States, this can be made much smaller.

        The encoding is split, to accomodate the fixed "version" bits of UUID.
          Bits 46-49 = the first three bits of the issuing country
          Bits 48-51 = 1000 (as per RFC 9562 table 2)
          Bits 52-58 = remaining bits of the issuing country

        Bits 59-71 = Executing Country
        Same encoding as Issuing Country. Also split, this time to accomodate the 
        fixed "variant" bits of the UUID.
          Bits 59-63 = first five bits of the executing country
          Bits 64-65 = 10 (as per RFC 9562 table 1)
          Bits 66-70 = the remaining bits of the executing country

        Bit 71 = Padding
        Adding a padding bit here makes the DES and Identifier ID align on octet
        boundaries, which means they can be more easily parsed if needed.

        Bits 71-103 = DES
        Encoded as an unsigned 32-bit integer

        Bits 104-127 = Identifier ID
        Encoded as an unsigned 24-bit integer

        ----------------------------
        DEFINITION OF GLOBAL CASE ID
        ----------------------------
        From EDS-MESSAGE.XSD

        GlobalCaseId must uniquely identify Legal Case among different instances of DES in different countries.
        GlobalCaseId has format:
        INSTRUMENT-ISSUING_COUNTRY-EXECUTING_COUNTRY-ISSUE_DATE-ISSUE_DATE_SEQUENCE-DES_NATIONAL_INSTANCE_ID
        Dash is a separator.
        Example value: “EIO-AT-PL-2019-09-10-0012-1”.

        INSTRUMENT:
        - Instrument of the Legal Case. Must match the Service used.
        - example: EIO

        ISSUING_COUNTRY:
        - Legal Case issuing country code in ISO-3166-1 Alpha 2 format.
        - In case of EioAnnexASubmissionAction carried XML form must have the same Annex A -> Section A -> Issuing
        country.
        - example: AT

        EXECUTING_COUNTRY:
        - Legal Case executing country code in ISO-3166-1 Alpha 2 format.
        - In case of EioAnnexASubmissionAction carried XML form must have the same Annex A -> Section A -> Executing
        country.
        - example: PL

        ISSUE_DATE:
        - Legal Case issue date. Dash is separator of the fields also in issue date.
        - example: 2019-09-10

        ISSUE_DATE_SEQUENCE:
        - Sequential number of the issued Legal Cases on a given date by specific DES instance to a specific executing
        country.
        - Starts with 0001.
        - example: 0012

        DES_NATIONAL_INSTANCE_ID:
        - DES instance unique positive (non-zero) id number. Uniqueness has to be preserved within same country.
        Different countries may have the same national instance id’s configured.
        - value has to be in range: from 0 to 2^31-1 (ZERO to max integer value in Java)
        - value CANNOT be prefixed with zero(s)
        - example (CORRECT) : 1234
        - example (INCORRECT): 01234


        ----------------------------------
        UUIDv4 layout (RFC9562) clause 5.4
        ----------------------------------

        0                   1                   2                   3
        0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |                           random_a                            |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |          random_a             |  ver  |       random_b        |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |var|                       random_c                            |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |                           random_c                            |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

        For UUIDv3

'''

INSTRUMENTS = [
    "EIO",
    "MLA",
    "ITN",
    "EPOC",
    "EPOCPR",
    "SODA",
    "SODB",
    "SODX",
    "TOEA",
    "TOEL",
    "TOEX",
]

GCI_REGEX = compile(r"(EIO|MLA|ITN|EPOC|EPOCPR|SODA|SODB|SODX|TOEA|TOEL|TOEX)-[A-Z]{2}-[A-Z]{2}-[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}-[0-9]+")
TID_REGEX = compile(r"(EIO|MLA|ITN|EPOC|EPOCPR|SODA|SODB|SODX|TOEA|TOEL|TOEX)-[A-Z]{2}-[A-Z]{2}-[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}-[0-9]+-[0-9]+")

class GlobalCaseID:
    MAX_DATE_SEQ = 9999
    MAX_NATIONAL_ID = (2^32) - 1
    MAX_IDENTIFIER_ID = (2^24) - 1
    
    @staticmethod
    def parse_from_gci(s: str):
        if not (TID_REGEX.match(s) or GCI_REGEX.match(s)):
            raise ValueError(f"String {s} does not match GCI ot TID regex")
        parts = s.split("-")
        return GlobalCaseID(parts[0],
            parts[1],
            parts[2],
            date.fromisoformat(f"{parts[3]}-{parts[4]}-{parts[5]}"),
            int(parts[6]),
            int(parts[7]),
            int(parts[8]) if len(parts) == 9 else None)

    @staticmethod
    def parse_from_uuid(s: str):
        uuid = UUID(s)
        i = uuid.int
        bits = int2ba(i,128)
        bits = bits[0:48] + bits[52:64] + bits[66:]        
        tid = ba2int(bits[98:122])
        tid = None if tid == 0 else tid
        return GlobalCaseID(
            INSTRUMENTS[ba2int(bits[41:45])],
            GlobalCaseID.int_to_country(ba2int(bits[45:55])),
            GlobalCaseID.int_to_country(ba2int(bits[55:65])),
            GlobalCaseID.int_to_date(ba2int(bits[0:27])),
            ba2int(bits[27:41]),
            ba2int(bits[66:98]),
            tid
            )

    @staticmethod
    def a_to_z_as_int(c: str):
        if len(c) != 1:
            raise ValueError(f"Input value {c} not a single character")
        return ord(c[0]) - ord('A')

    @staticmethod
    def int_as_a_to_z(i: int):
        return chr(i + ord('A'))

    @staticmethod
    def country_to_int(c: str):
        if len(c) != 2:
            raise ValueError(f"Input value {c} not a valid ISO country code")
        i_hi = GlobalCaseID.a_to_z_as_int(c[0])
        i_lo = GlobalCaseID.a_to_z_as_int(c[1])
        return (i_hi * 26) + i_lo

    @staticmethod
    def int_to_country(i: int):
        i_hi = int(i / 26)
        i_lo = i % 26
        return GlobalCaseID.int_as_a_to_z(i_hi) + GlobalCaseID.int_as_a_to_z(i_lo)
    
    @staticmethod
    def date_to_int(d: date):
        return int(d.isoformat().replace("-",""))

    @staticmethod
    def int_to_date(i: int):
        s = str(i)
        s = f"{s[0:4]}-{s[4:6]}-{s[6:8]}"
        return date.fromisoformat(s)

    def __init__(self, instrument, issuing_country, executing_country, issue_date, issue_date_sequence, national_id, identifier_id = None):
        self.instrument = instrument
        self.issuing_country = issuing_country
        self.executing_country = executing_country
        self.issue_date = issue_date
        self.issue_date_sequence = issue_date_sequence
        self.national_id = national_id
        self.identifier_id = identifier_id

    def __repr__(self):
        return f"<GlobalCaseID {self.as_gci()}>"

    def as_gci(self):
        return f"{self.instrument}-{self.issuing_country}-{self.executing_country}-{self.issue_date.isoformat()}-{self.issue_date_sequence:04}-{self.national_id}{'-' + str(self.identifier_id) if self.identifier_id else ''}"

    def as_uuid(self):
        b_issuing_country = int2ba(GlobalCaseID.country_to_int(self.issuing_country), 10)
        b_executing_country = int2ba(GlobalCaseID.country_to_int(self.executing_country), 10)
    
        bits = (int2ba(GlobalCaseID.date_to_int(self.issue_date), 27) +               # Bits 0-26 = Date
                int2ba(self.issue_date_sequence, 14) +                                # Bits 27-41 = Date sequence
                int2ba(INSTRUMENTS.index(self.instrument),4) +                        # Bits 42-45 = Instrument type
                b_issuing_country[0:3] + int2ba(4,4) + b_issuing_country[3:] +        # Bits 46-59 = Issuing country (incl 4 fixed UUIDv4 "version" bits)
                b_executing_country[0:5] + int2ba(2,2) + b_executing_country[5:] +    # Bits 59-71 = Executing country (incl 2 fixed UUIDv4 "variant" bits)
                int2ba(0,1) +                                                         # 1 bit padding (to make DES and Identifier ID align on octet boundaries)
                int2ba(self.national_id, 32) +                                        # Bits 71-103 = DES
                int2ba(self.identifier_id if self.identifier_id else 0, 24))          # Bits 104-127 = Identifier ID

        i_uuid = ba2int(bits)

        u = UUID(int = i_uuid)
        return u

def uuid_from_global_case_id (global_case_id: str):
    return GlobalCaseID.parse_from_gci(global_case_id)

def global_case_id_from_uuid (uuid: str):
    return GlobalCaseID.parse_from_uuid(uuid)


from sys import argv

def gci2uuid () -> None:
    gci = GlobalCaseID.parse_from_gci(argv[1])
    uuid = gci.as_uuid()
    print(uuid)

def uuid2gci () -> None:
    gci = GlobalCaseID.parse_from_uuid(argv[1])
    print(gci.as_gci())
