from multiprocessing import Lock, Value, Queue
from ctypes import c_bool

#placed here temporarily as legacy from BonAppetit
""" Function success/failure """
FAILURE = "FAILURE"
SUCCESS = "SUCCESS"
WARNING = "WARNING"

# Default maximum queue size use -1 to set to "infinite"
_HIGHEST_ALLOWABLE_Q_SIZE = 1000000
DEFAULT_MAX_Q_SIZE = 10000


# Class Name: CustomQueue
# Description: A customized queue object for MacOS and Windows with Multiprocessing support
class CustomQueue(object):
    def __init__(self, maxQsize=DEFAULT_MAX_Q_SIZE):
        # Define the queue object
        self.__q = Queue()
        # Define the queue access flag
        self.__is_accessible  = Value(c_bool, False)
        # Define the queue size
        self.__size = Value('i', 0)
        # Define the maximum size of the queue
        self.__maxSize = Value('i', 0)
        # Add a lock to the class
        self.__lock = Lock()
        # Initialize the queue max size
        self.setMaxSize(maxQsize)

    """****************************************** Max Size set/get functions ****************************************"""

    # Function Name: getMaxSize
    # Arguments: None
    # Returns: Returns the current max size value
    # Description: Returns the configured max size of the queue
    def getMaxSize(self):
        with self.__lock:
            # Get a snapshot of the maxsize value
            value = self.__maxSize.value
            # Return the value based on range
            return value if value > 0 else DEFAULT_MAX_Q_SIZE

    # Function Name: setMaxSize
    # Arguments: maxQsize (The new max size value)
    # Returns: SUCCESS (if value is valid)
    # Description: Re-configures max size of the queue
    def setMaxSize(self, maxQsize=DEFAULT_MAX_Q_SIZE):
        with self.__lock:
            # Check the input type
            if not isinstance(maxQsize, int): # pyright: ignore[reportUnnecessaryIsInstance]
                return WARNING, "Expected max queue value of type <int> received <{}>".format(type(maxQsize))

            # Check the input range
            if maxQsize <= 0:
                self.__maxSize.value = DEFAULT_MAX_Q_SIZE
                return WARNING, "Expected a value greater than zero received <{}>".format(str(maxQsize))

            self.__maxSize.value = max(0, min(_HIGHEST_ALLOWABLE_Q_SIZE, maxQsize))

            # Resize the queue back if larger than new max size
            while self.__size.value > self.__maxSize.value:
                self.__size.value -= 1
                self.__q.get()

            return SUCCESS, "Max queue size updated to <{}> successfully".format(str(self.__maxSize.value))

    """**************************************** Queue flag set/get functions ****************************************"""

    # Function Name: setFlag
    # Arguments: None
    # Returns: None
    # Description: Sets the flag to true
    def setFlag(self):
        with self.__lock:
            self.__is_accessible.value = True

    # Function Name: releaseFlag
    # Arguments: None
    # Returns: None
    # Description: Sets the flag to false
    def releaseFlag(self):
        with self.__lock:
            self.__is_accessible.value = False

    # Function Name: getFlag
    # Arguments: None
    # Returns: Flag status
    # Description: Returns the current flag value
    def getFlag(self):
        with self.__lock:
            return self.__is_accessible.value

    """******************************************* Queue operation functions ****************************************"""

    # Function Name: put
    # Arguments: data (The data to be added)
    # Returns: SUCCESS if data is added
    # Description: Adds data to queue if enabled
    def put(self, data):
        with self.__lock:
            # Check queue flag
            if self.__is_accessible.value:
                # Add new data to queue
                self.__size.value += 1
                self.__q.put(data)
                # Maintain the queue size if specified
                while self.__size.value > self.__maxSize.value > 0:
                    self.__size.value -= 1
                    self.__q.get()
                return SUCCESS, "CustomQueue: Data added to queue"
            return FAILURE, "CustomQueue: Queue access denied! Can't write to queue"

    # Function Name: get
    # Arguments: None
    # Returns: Data in queue FIFO based
    # Description: Returns data from queue if enabled and is not empty
    def get(self):
        with self.__lock:
            if self.__is_accessible.value:
                if self.__size.value > 0:
                    self.__size.value -= 1
                    return self.__q.get()
                return []
            return FAILURE, "CustomQueue: Queue access denied! Can't read from queue"

    # Function Name: getAll
    # Arguments: None
    # Returns: Data in queue FIFO based
    # Description: Returns all data from queue if enabled
    def getAll(self):
        with self.__lock:
            if self.__is_accessible.value:
                queueData = list()
                while self.__size.value > 0:
                    self.__size.value -= 1
                    queueData.append(self.__q.get())
                return queueData
            return FAILURE, "CustomQueue: Queue access denied! Can't read from queue"

    """********************************************* Queue status functions *****************************************"""

    # Function Name: clear
    # Arguments: None
    # Returns: None
    # Description: Clears all the data in the queue
    def clear(self):
        with self.__lock:
            while self.__size.value > 0:
                self.__size.value -= 1
                self.__q.get()

    # Function Name: empty
    # Arguments: None
    # Returns: Bool indicating if queue is empty (True) or not (False)
    # Description: Checks if the queue is empty
    def empty(self):
        with self.__lock:
            return (self.__size.value <= 0)

    # Function Name: qsize
    # Arguments: None
    # Returns: Integer indicating the size of the queue
    # Description: Returns the current size of the queue
    def qsize(self):
        with self.__lock:
            return self.__size.value

if __name__=="__main__":
    q = CustomQueue(maxQsize=4)
    q.setFlag()
    assert q.qsize() == 0
    assert q.empty()

    q.put(5)
    q.put(6)

    assert q.qsize() == 2
    assert not q.empty()

    assert q.get() == 5
    assert q.get() == 6
    assert q.get() == []

    q.put(7)
    q.put(8)
    q.put(9)
    q.put(10)

    assert q.getAll() == [7, 8, 9, 10]


    q.put(7)
    q.put(8)
    q.put(9)
    q.put(10)
    q.put(11)
    q.put(12)

    assert q.qsize() == 4
    assert q.getAll() == [9, 10, 11, 12]
    assert q.getAll() == []
    assert q.getAll() == []

    q.put(7)
    q.put(8)
    q.put(9)
    q.put(10)

    q.releaseFlag()
    assert q.getAll()[0] == FAILURE

    q.setFlag()
    assert q.put(11)[0] == SUCCESS
    q.releaseFlag()
    assert q.put(12)[0] == FAILURE
    
    q.setFlag()
    assert q.get() == 8
    assert q.get() == 9
    assert q.getAll() == [10, 11]

    q.put(7)
    q.put(8)
    q.put(9)
    q.put(10)

    assert q.qsize() == 4
    q.setMaxSize(2)
    assert q.qsize() == 2
    assert q.getMaxSize() == 2
    assert q.get() == 9
    assert q.get() == 10

    q.put(7)
    q.put(8)
    assert q.qsize() == 2
    q.setMaxSize(5)
    assert q.qsize() == 2
    assert q.getMaxSize() == 5
    assert q.get() == 7
    assert q.get() == 8



