import { OperationType } from "./OperationType.js";

/**
 *
 * @param {Operation[]} queue
 */
export function optimizeCommandQueue(queue) {

    /**
     * TODO this is pretty slow and should be optimized
     * @param {Array.<Operation>} queue
     */
    function cutRedundantOperations(queue) {
        const queueLength = queue.length;
        loop_Main: for (let i = queueLength - 1; i >= 0; i--) {
            const opA = queue[i];
            if (opA.operator === OperationType.Add) {
                //nothing to do
                continue;
            }
            const operandsA = opA.operands;
            if (opA.operator === OperationType.Remove) {
                //hunt and delete operations before this remove that affect deleted references
                for (let k = 0, kl = operandsA.length; k < kl; k++) {
                    const refA = operandsA[k];

                    //walk the queue back and remove all previous operations on these references
                    loop_B: for (let j = i - 1; j >= 0; j--) {
                        const opB = queue[j];
                        const operandsB = opB.operands;
                        const operatorB = opB.operator;
                        if (operatorB === OperationType.WriteAttribute) {
                            const refB = operandsB[0];
                            if (refA === refB) {
                                //cut operation
                                queue.splice(j, 1);
                                i--;
                            }
                        } else if (operatorB === OperationType.Add) {
                            for (let m = 0, ml = operandsB.length; m < ml; m++) {
                                const refB = operandsB[m];
                                if (refA === refB) {
                                    let jump = 0;
                                    //delete operation and add operation cancel each other out
                                    if (kl > 1) {
                                        operandsA.splice(k, 1);
                                        kl--;
                                        k--;
                                    } else {
                                        queue.splice(i, 1);
                                        i--;
                                        jump |= 2;
                                    }

                                    if (ml > 1) {
                                        operandsB.splice(m, 1);
                                        m--;
                                        ml--;
                                    } else {
                                        //cut operation from the queue
                                        queue.splice(j, 1);
                                        i--;
                                        jump |= 1;
                                    }

                                    if ((jump & 2) !== 0) {
                                        continue loop_Main;
                                    } else if (jump !== 0) {
                                        continue loop_B;
                                    }
                                }
                            }
                        }
                    }
                }
            } else if (opA.operator === OperationType.WriteAttribute) {
                //find other operations that modify the same attribute, any previous operations are redundant at this point
                const refA = operandsA[0];
                const attributeIndexA = operandsA[1];
                //walk the queue back and remove all previous operations on these references
                for (let j = i - 1; j >= 0; j--) {
                    const opB = queue[j];
                    if (opB.operator !== OperationType.WriteAttribute) {
                        //ignore
                        continue;
                    }

                    const operandsB = opB.operands;
                    if (operandsB[0] === refA && operandsB[1] === attributeIndexA) {
                        //operation will be overwritten anyway, cut it
                        queue.splice(j, 1);
                        i--;
                    }
                }
            }
        }
    }

    /**
     *
     * @param {Array.<Operation>} queue
     */
    function groupOperations(queue) {
        let l = queue.length;
        for (let i = 0; i < l; i++) {
            const opA = queue[i];
            const operatorA = opA.operator;
            const operandsA = opA.operands;
            if (operatorA === OperationType.Add || operatorA === OperationType.Remove) {
                for (let j = i + 1; j < l; j++) {
                    const opB = queue[j];
                    if (opB.operator === operatorA) {
                        const operandsB = opB.operands;
                        const numOperands = operandsB.length;

                        for (let k = 0; k < numOperands; k++) {
                            const refB = operandsB[k];
                            if (operandsA.indexOf(refB) === -1) {
                                operandsA.push(refB);
                            }
                        }

                        queue.splice(j, 1);
                        j--;
                        l--;
                    }
                }
            }
        }
    }

    /**
     * @param {Array.<Operation>} queue
     */
    function sortOperations(queue) {
        queue.sort(function (a, b) {
            const operatorA = a.operator;
            const operatorB = b.operator;

            //it's important that Add and Remove operations are sorted to the front of the queue, before Write operations
            if (operatorA < operatorB) {
                return -1;
            } else if (operatorB > operatorA) {
                return 1;
            }

            if (operatorA !== OperationType.WriteAttribute) {
                return 0;
            }

            const attributeIndexA = a.operands[1];
            const attributeIndexB = b.operands[1];

            //sort write operations by buffer index
            return attributeIndexB - attributeIndexA;
        });
    }

    cutRedundantOperations(queue);
    groupOperations(queue);
    sortOperations(queue);
}
