<p>You are given <code>n</code>​​​​​​ tasks labeled from <code>0</code> to <code>n - 1</code> represented by a 2D integer array <code>tasks</code>, where <code>tasks[i] = [enqueueTime<sub>i</sub>, processingTime<sub>i</sub>]</code> means that the <code>i<sup>​​​​​​th</sup></code>​​​​ task will be available to process at <code>enqueueTime<sub>i</sub></code> and will take <code>processingTime<sub>i</sub></code><sub> </sub>to finish processing.</p>

<p>You have a single-threaded CPU that can process <strong>at most one</strong> task at a time and will act in the following way:</p>

<ul>
	<li>If the CPU is idle and there are no available tasks to process, the CPU remains idle.</li>
	<li>If the CPU is idle and there are available tasks, the CPU will choose the one with the <strong>shortest processing time</strong>. If multiple tasks have the same shortest processing time, it will choose the task with the smallest index.</li>
	<li>Once a task is started, the CPU will <strong>process the entire task</strong> without stopping.</li>
	<li>The CPU can finish a task then start a new one instantly.</li>
</ul>

<p>Return <em>the order in which the CPU will process the tasks.</em></p>

<p>&nbsp;</p>
<p><strong class="example">Example 1:</strong></p>

<pre>
<strong>Input:</strong> tasks = [[1,2],[2,4],[3,2],[4,1]]
<strong>Output:</strong> [0,2,3,1]
<strong>Explanation: </strong>The events go as follows: 
- At time = 1, task 0 is available to process. Available tasks = {0}.
- Also at time = 1, the idle CPU starts processing task 0. Available tasks = {}.
- At time = 2, task 1 is available to process. Available tasks = {1}.
- At time = 3, task 2 is available to process. Available tasks = {1, 2}.
- Also at time = 3, the CPU finishes task 0 and starts processing task 2 as it is the shortest. Available tasks = {1}.
- At time = 4, task 3 is available to process. Available tasks = {1, 3}.
- At time = 5, the CPU finishes task 2 and starts processing task 3 as it is the shortest. Available tasks = {1}.
- At time = 6, the CPU finishes task 3 and starts processing task 1. Available tasks = {}.
- At time = 10, the CPU finishes task 1 and becomes idle.
</pre>

<p><strong class="example">Example 2:</strong></p>

<pre>
<strong>Input:</strong> tasks = [[7,10],[7,12],[7,5],[7,4],[7,2]]
<strong>Output:</strong> [4,3,2,0,1]
<strong>Explanation</strong><strong>: </strong>The events go as follows:
- At time = 7, all the tasks become available. Available tasks = {0,1,2,3,4}.
- Also at time = 7, the idle CPU starts processing task 4. Available tasks = {0,1,2,3}.
- At time = 9, the CPU finishes task 4 and starts processing task 3. Available tasks = {0,1,2}.
- At time = 13, the CPU finishes task 3 and starts processing task 2. Available tasks = {0,1}.
- At time = 18, the CPU finishes task 2 and starts processing task 0. Available tasks = {1}.
- At time = 28, the CPU finishes task 0 and starts processing task 1. Available tasks = {}.
- At time = 40, the CPU finishes task 1 and becomes idle.
</pre>

<p>&nbsp;</p>
<p><strong>Constraints:</strong></p>

<ul>
	<li><code>tasks.length == n</code></li>
	<li><code>1 &lt;= n &lt;= 10<sup>5</sup></code></li>
	<li><code>1 &lt;= enqueueTime<sub>i</sub>, processingTime<sub>i</sub> &lt;= 10<sup>9</sup></code></li>
</ul>

class Solution:
    def getOrder(self, tasks: List[List[int]]) -> List[int]:
        # single threaded CPU can process at most one task at a time 
        # enqueueTime = time when task available to be processed (startTime has to be at 
        # enqueTime or later)

        # processingTime = how long it will take to process (once we start the process)
        # we know the thread will be working for this amount of time 

        # if task list empty 
        # CPU -> idle
        # else
        # pick shortest processing time (if multiple, then choose smallest index)
        # once task is started, cpu will process the entire task without stopping 
        # cpu can finish a task then start a new one instantly 

        # get the order in which the cpu will process task 

        # edge case -> tasks (empty)
        if not tasks: 
            return tasks 
        
        # examples:[[1,2],[2,4],[3,2],[4,1]]
        # shortest to longest [4,1][1,2][3,2][2,4]
        # at time 1 -> task 0 is avilable to process, available {0}
        # {0} started -> 2 
        # at time 2 -> {1}
        # at time 3 -> {1, 2}
        # at time 3 -> finishes task 0, next one is 2 since it's shortest 
        # {1}
        # at time 4 -> {1, 3}
        # at time 5 -> finishes task 2, picks up task 3 
        # at time 6 -> { done 3, starts 1}
        # answer [0,2,3,1]

        # second example, sort by starting time 4:[7,2] 3:[7,4] 2:[7,5] 0:[7,10], 1:[7,12]
        # iterate through the tasks, either put them in list and do a sort by 
        # endTime or use a heap 
        # 4,3,2,0,1 

        # if we use a heap, can always look up which task is in works -> acts as what we put in the CPU 
        # things we need to consider: 
            # example 2: start time may be all the same or partially same, 
            # sort by [starttime, endtime, index] 
        
        for i in range(len(tasks)):
            enqueue_time, process_time = tasks[i]
            tasks[i] = (enqueue_time, process_time, i)
        
        tasks.sort()
        # by sorting, [[1,2,0], [2,4,1], [3,2,2], [4,1,3]]
        # second example [[7,2,4], [7,4,3], [7,5,2], [7,10,0], [7,12,1]]

        # until we finish processing the now sorted tasks 
        start_time = tasks[0][0]
        available_tasks = [] # use it as heap
        ordered_tasks = [] 
        # keep track of when the cpu_thread task ends 
        
        i = 0
        while i < len(tasks):
            if tasks[i][0] == start_time: 
                # push all the tasks that has the same start time as the first in the list
                heapq.heappush(available_tasks, (tasks[i][1], tasks[i][2]))
            else: 
                break 
            
            i += 1

        while available_tasks: 
            # enqueueTime + processingTime of the one being processed in cpu 
            process = heapq.heappop(available_tasks)
            #print(f'process is {process}')
            ordered_tasks.append(process[1])
            start_time += process[0]

            while i < len(tasks) and tasks[i][0] <= start_time: 
                heapq.heappush(available_tasks, (tasks[i][1], tasks[i][2]))
                i += 1 

            if not available_tasks and i < len(tasks):
                start_time = tasks[i][0]
                while i < len(tasks):
                    if tasks[i][0] == start_time:
                        heapq.heappush(available_tasks, (tasks[i][1], tasks[i][2]))
                    else: 
                        break 
                    i += 1 

        return ordered_tasks
                

            
            
            
            
62300