"""uuid.py: Thin bifrost wrapper around python uuid (universally unique identifier) module."""

# NB: at present the code does not enforce multiprocessor safe generation of unique identifiers
# it calls uuid.uuid1() to generate new uuids, ie. using version 1 of the iso standard options

version = '15th June 2021'

# import logging
# log = logging.getLogger(__name__)

import uuid

test_mode = False
test_latest_int = 0

max_version_string_length = 10


def switch_on_test_mode(seed = 0):
   """Causes subsequent calls to new_uid() to produce integer sequence starting from successor to seed.

      argument:
         seed (integer, default 0): The predecessor to the first uuid returned by subsequent calls to
            new_uuid()

      returns:
         None

      notes:
         call switch_off_test_mode() to reactivate normal behaviour;
         uuids generated whilst in test mode do not adhere to the iso standard;
         test mode is intended to allow replicatable behaviour for testing purposes
   """

   global test_mode
   global test_latest_int
   test_latest_int = seed
   test_mode = True


def switch_off_test_mode():
   """Subsequent calls to new_uid() will produce standard uuid values (default behaviour).

      note:
         this function will have no effect unless switch_on_test_mode() has previously been called
   """

   global test_mode
   test_mode = False


def new_uuid():
   """Returns a new uuid based on the time (to 100ns) & MAC address option of the iso standard.

      returns:
         uuid.UUID object

      notes:
         at present, the multi-processor safe functionality is not deployed, so multiple processors
         sharing the same MAC address could generate the same uuid simultaneously;
         an integer sequence is generated when in test mode
   """

   global test_latest_int
   if test_mode:
      test_latest_int += 1
      return uuid.UUID(bytes = test_latest_int.to_bytes(16, byte_order = 'big'))
   else:
      return uuid.uuid1()   # time to 100ns & MAC address


def string_from_uuid(uuid_obj):
   """Returns standard hexadecimal string for uuid; same as str(uuid_obj).

      argument:
         uuid_obj (uuid.UUID object): the uuid which is required in hexadecimal string format

      returns:
         string (40 characters: 36 lowercase hexadecimals and 4 hyphens)
   """

   return str(uuid_obj)


def uuid_from_string(uuid_str):
   """Returns a uuid object for the given uuid string; hyphens are ignored.

      argument:
         uuid_str (string): the hexadecimal representation of the 128 bit uuid integer;
            hyphens are ignored

      returns:
         uuid.UUID object

      notes:
         if a uuid.UUID object is passed by accident, it is returned;
         if the string starts with an underscore, it is skipped (to cater for a fesapi quirk)
   """

   if uuid_str is None: return None
   if isinstance(uuid_str, uuid.UUID): return uuid_str    # resilience to accidentally passing a uuid object
   if len(uuid_str) < 36: return None
   if uuid_str[0] == '_':    # tolerate one of the fesapi quirks
      return uuid.UUID(uuid_str[1:])
   else:
      return uuid.UUID(uuid_str)


def uuid_as_bytes(uuid_obj):
   """Returns the uuid as a 16 byte bytes sequence; same as uuid_obj.bytes.

      argument:
         uuid_obj (uuid.UUID object): the uuid for which a bytes representation is required

      returns:
         bytes (16 bytes long)
   """

   if uuid_obj is None: return None
   if isinstance(uuid_obj, str): uuid_obj = uuid_from_string(uuid_obj)  # resilience to accidental string arg
   return uuid_obj.bytes


def uuid_as_int(uuid_obj):
   """Returns the uuid as a 128 bit int; same as uuid_obj.int.

      argument:
         uuid_obj (uuid.UUID object): the uuid for which a bytes representation is required

      returns:
         bytes (16 bytes long)
   """

   if uuid_obj is None: return None
   if isinstance(uuid_obj, str): uuid_obj = uuid_from_string(uuid_obj)  # resilience to accidental string arg
   return uuid_obj.int


def matching_uuids(uuid_a, uuid_b):
   """Returns True if the 2 uuid objects are for the same id; False otherwise.

      arguments:
         uuid_a, uuid_b (uuid.UUID objects): the two uuids to be compared

      returns:
         boolean: True if the two uuids are the same; False otherwise

      note:
         this function is resilient to uuids being passed in hexadecimal string format
   """

   if isinstance(uuid_a, str): uuid_a = uuid_from_string(uuid_a)  # resilience to accidental string arg
   if isinstance(uuid_b, str): uuid_b = uuid_from_string(uuid_b)
   if uuid_a is None or uuid_b is None: return False
   return uuid_a.int == uuid_b.int


def version_string(uuid_obj):
   """Returns an integer string rendering of the time element of the uuid.

      argument:
         uuid_obj (uuid.UUID): the uuid for which a string representation of the time component is required

      returns:
         string (of digits)

      notes:
         this function has nothing to do with the uuid.version attribute, it is used to populate the version
         field of a resqml citation block;
         the time component of the uuid is the number of 100ns periods that have elapsed since October 1582
         (when the Gregorian calendar was adopted), as a 60 bit integer
   """

   if isinstance(uuid_obj, str): uuid_obj = uuid_from_string(uuid_obj)  # resilience to accidental string arg
   v_str = str(uuid_obj.time)
   if len(v_str) > max_version_string_length:
      v_str = v_str[:max_version_string_length]
   return v_str
