"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const traverse_1 = require("@glas/traverse");
const ast_1 = require("../ast");
const createScopeMaps_1 = __importDefault(require("../createScopeMaps"));
const common_1 = require("../common");
const types_1 = require("../types");
const isConsequent_1 = __importDefault(require("../analysis/isConsequent"));
/*
export data struct Vector
    var x: Number = 0
    var y: Number = 0
    static class Array
        x: Float64Array
        // y: Float64Array
        get(i: Integer) =>
            return new Vector(this.x[i], this.x[i + 1])
        set(i: Integer, value: Vector) =>
            this.x[i] = value.x
            this.y[i+1] = value.y
        [Symbol.iterator]() =>
            // iteration here
*/
let fieldTypes = {
    Float64: 8,
    Float32: 4,
    Int32: 4,
    Int16: 2,
    Int8: 1,
    UInt32: 4,
    UInt16: 2,
    UInt8: 1,
};
// let's start out WITHOUT recursion, then add that after
function getFieldType(type, scopes, ancestors) {
    if (ast_1.Reference.is(type)) {
        let fieldClass = common_1.getOriginalDeclaration(type, scopes, ancestors, ast_1.ClassDeclaration.is);
        if (fieldClass != null) {
            return type;
        }
        let variableDeclaration = common_1.getOriginalDeclaration(type, scopes, ancestors, ast_1.VariableDeclaration.is);
        if (variableDeclaration != null) {
            if (variableDeclaration.kind !== "type") {
                throw common_1.SemanticError("Expected a type declaration", type);
            }
            type = variableDeclaration.value;
        }
    }
    if (ast_1.TypeExpression.is(type)) {
        for (let name in types_1.IntegerTypes) {
            let integerType = types_1.IntegerTypes[name];
            if (isConsequent_1.default(type, integerType)) {
                return name;
            }
        }
    }
    return "Float64";
}
function assignActualFields(fields, actualFields = Array()) {
    let actualOffset = 0;
    for (let field of fields) {
        // try to find a pre-existing field that works.
        let actualIndex = actualFields.findIndex(a => {
            if (a.type === field.type) {
                let relativeOffset = field.offset - a.offset;
                let offsetByTypeSize = relativeOffset / fieldTypes[a.type];
                if (Number.isInteger(offsetByTypeSize)) {
                    actualOffset = offsetByTypeSize;
                    return true;
                }
            }
            return false;
        });
        if (actualIndex < 0) {
            actualIndex = actualFields.length;
            if (ast_1.Reference.is(field.type)) {
                assignActualFields(field.subfields, actualFields);
            }
            else {
                let actualField = { name: field.path, type: field.type, offset: field.offset, stride: -1 };
                actualFields.push(actualField);
            }
        }
        // set field info
        field.actualField = actualIndex;
        field.actualOffset = actualOffset;
    }
    return actualFields;
}
function getFieldInfos(cls, scopes, ancestors, offset, path = []) {
    let fields = new Array();
    for (let d of cls.instance.declarations) {
        if (d.kind === "var") {
            if (d.type == null) {
                throw common_1.SemanticError("Type required on data struct properties", d);
            }
            let { name } = d.id;
            let type = getFieldType(d.type, scopes, ancestors);
            let subfields;
            if (ast_1.Reference.is(type)) {
                let fieldClass = common_1.getOriginalDeclaration(type, scopes, ancestors, ast_1.ClassDeclaration.is);
                if (fieldClass == null) {
                    throw common_1.SemanticError(`Data struct not found`, type);
                }
                if (!fieldClass.isData || !fieldClass.isStruct) {
                    throw common_1.SemanticError(`Data struct fields can only be Numbers or data structs`, fieldClass.id);
                }
                let result = getFieldInfos(fieldClass, scopes, ancestors, offset, path.concat(name));
                subfields = result[0];
                offset = result[1];
            }
            fields.push({ name, path: (path.concat(name)).join('_'), offset, type, subfields, actualField: 0, actualOffset: 0 });
            if (!ast_1.Reference.is(type)) {
                let fieldSize = fieldTypes[type];
                offset += fieldSize;
            }
        }
    }
    return [fields, offset];
}
function createTypedArrayDeclaration(cls, fields, actualFields, sizeInBytes, options) {
    function createFieldGetterOrSetter(field, setFrom) {
        if (ast_1.Reference.is(field.type)) {
            let { name } = field;
            // field.name
            let subfields = field.subfields.map(field => createFieldGetterOrSetter(field, setFrom
                ? new ast_1.MemberExpression({ object: setFrom, property: new ast_1.Identifier({ name }) })
                : setFrom));
            return setFrom ? subfields : new ast_1.CallExpression({ new: true, callee: field.type, arguments: subfields });
        }
        let actualField = actualFields[field.actualField];
        let indexer = new ast_1.BinaryExpression({
            left: new ast_1.Reference({ name: "index" }),
            operator: "*",
            right: new ast_1.Literal({ value: actualField.stride })
        });
        if (field.actualOffset > 0) {
            indexer = new ast_1.BinaryExpression({
                left: indexer, operator: "+", right: new ast_1.Literal({ value: field.actualOffset })
            });
        }
        let expression = new ast_1.MemberExpression({
            object: new ast_1.MemberExpression({
                object: new ast_1.ThisExpression({}),
                property: new ast_1.Identifier({ name: actualField.name })
            }),
            property: indexer
        });
        return setFrom ? new ast_1.AssignmentStatement({
            left: expression,
            operator: "=",
            right: new ast_1.MemberExpression({
                object: setFrom,
                property: new ast_1.Identifier({ name: field.name })
            })
        }) : expression;
    }
    // get function
    return new ast_1.VariableDeclaration({
        static: new ast_1.Identifier({ name: "static" }),
        kind: "var",
        id: new ast_1.Declarator({ name: "Array" }),
        value: new ast_1.ClassDeclaration({
            id: new ast_1.Declarator({ name: `${cls.id.name}Array` }),
            instance: new ast_1.InstanceDeclarations({
                declarations: [
                    // BEGIN CONSTRUCTOR
                    new ast_1.VariableDeclaration({
                        kind: "let",
                        id: new ast_1.Declarator({ name: "constructor" }),
                        value: new ast_1.FunctionExpression({
                            params: [
                                new ast_1.Parameter({
                                    id: new ast_1.Declarator({ name: "length" }),
                                    type: new ast_1.Reference({ name: "Integer" })
                                })
                            ],
                            body: new ast_1.BlockStatement({
                                body: [
                                    new ast_1.VariableDeclaration({
                                        kind: "let",
                                        id: new ast_1.Declarator({ name: "buffer" }),
                                        value: new ast_1.CallExpression({
                                            new: true,
                                            callee: new ast_1.Reference({ name: "ArrayBuffer" }),
                                            arguments: [
                                                new ast_1.BinaryExpression({
                                                    left: new ast_1.Reference({ name: "length" }),
                                                    operator: "*",
                                                    right: new ast_1.Literal({ value: sizeInBytes })
                                                })
                                            ]
                                        })
                                    }),
                                    new ast_1.AssignmentStatement({
                                        left: new ast_1.MemberExpression({
                                            object: new ast_1.ThisExpression({}),
                                            property: new ast_1.Identifier({ name: "length" })
                                        }),
                                        right: new ast_1.Reference({ name: "length" })
                                    }),
                                    ...actualFields.map(field => new ast_1.AssignmentStatement({
                                        left: new ast_1.MemberExpression({
                                            object: new ast_1.ThisExpression({}),
                                            property: new ast_1.Identifier({ name: field.name })
                                        }),
                                        right: new ast_1.CallExpression({
                                            new: true,
                                            callee: new ast_1.Reference({ name: `${field.type}Array` }),
                                            arguments: [
                                                new ast_1.Reference({ name: "buffer" }),
                                                new ast_1.Literal({ value: field.offset }),
                                            ]
                                        })
                                    })),
                                    options.debug ? new ast_1.ExpressionStatement({
                                        expression: new ast_1.CallExpression({
                                            callee: new ast_1.MemberExpression({
                                                object: new ast_1.Reference({ name: "Object" }),
                                                property: new ast_1.Identifier({ name: "freeze" }),
                                            }),
                                            arguments: [new ast_1.ThisExpression({})]
                                        })
                                    }) : []
                                ].flat()
                            })
                        })
                    }),
                    // BEGIN GETTER
                    new ast_1.VariableDeclaration({
                        kind: "let",
                        id: new ast_1.Declarator({ name: "get" }),
                        value: new ast_1.FunctionExpression({
                            params: [
                                new ast_1.Parameter({
                                    id: new ast_1.Declarator({ name: "index" })
                                })
                            ],
                            body: new ast_1.BlockStatement({
                                body: [
                                    new ast_1.ReturnStatement({
                                        argument: new ast_1.CallExpression({
                                            new: true,
                                            callee: new ast_1.Reference({ name: cls.id.name }),
                                            arguments: fields.map(field => createFieldGetterOrSetter(field)),
                                        })
                                    })
                                ]
                            })
                        })
                    }),
                    // BEGIN SETTER
                    new ast_1.VariableDeclaration({
                        kind: "let",
                        id: new ast_1.Declarator({ name: "set" }),
                        value: new ast_1.FunctionExpression({
                            params: [
                                new ast_1.Parameter({
                                    id: new ast_1.Declarator({ name: "index" })
                                }),
                                new ast_1.Parameter({
                                    id: new ast_1.Declarator({ name: "value" })
                                }),
                            ],
                            body: new ast_1.BlockStatement({
                                body: fields.map(field => createFieldGetterOrSetter(field, new ast_1.Reference({ name: "value" }))).flat()
                            })
                        })
                    }),
                    //  BEGIN ITERATOR
                    new ast_1.VariableDeclaration({
                        kind: "let",
                        id: new ast_1.MemberExpression({
                            object: new ast_1.Reference({ name: "Symbol" }),
                            property: new ast_1.Identifier({ name: "iterator" }),
                        }),
                        value: new ast_1.FunctionExpression({
                            generator: true,
                            params: [],
                            body: new ast_1.BlockStatement({
                                body: [
                                    new ast_1.VariableDeclaration({
                                        kind: "let",
                                        id: new ast_1.Declarator({ name: "length" }),
                                        value: new ast_1.MemberExpression({
                                            object: new ast_1.ThisExpression({}),
                                            property: new ast_1.Identifier({ name: "length" }),
                                        })
                                    }),
                                    new ast_1.ForStatement({
                                        init: new ast_1.VariableDeclaration({
                                            kind: "var",
                                            id: new ast_1.Declarator({ name: "i" }),
                                            value: new ast_1.Literal({ value: 0 })
                                        }),
                                        test: new ast_1.BinaryExpression({
                                            left: new ast_1.Reference({ name: "i" }),
                                            operator: "<",
                                            right: new ast_1.Reference({ name: "length" }),
                                        }),
                                        update: new ast_1.UnaryExpression({
                                            prefix: false,
                                            argument: new ast_1.Reference({ name: "i" }),
                                            operator: "++"
                                        }),
                                        body: new ast_1.BlockStatement({
                                            body: [
                                                new ast_1.ExpressionStatement({
                                                    expression: new ast_1.YieldExpression({
                                                        argument: new ast_1.CallExpression({
                                                            callee: new ast_1.MemberExpression({
                                                                object: new ast_1.ThisExpression({}),
                                                                property: new ast_1.Identifier({ name: "get" }),
                                                            }),
                                                            arguments: [new ast_1.Reference({ name: "i" })],
                                                        })
                                                    })
                                                })
                                            ]
                                        })
                                    })
                                ]
                            })
                        })
                    }),
                ]
            }),
            static: []
        })
    });
}
function addTypedStructArrays(root, options) {
    let ancestors = new Map();
    let scopes = createScopeMaps_1.default(root, { ancestorsMap: ancestors });
    return traverse_1.traverse(root, {
        enter(node) {
            if (ast_1.TypeExpression.is(node)) {
                return traverse_1.skip;
            }
        },
        leave(node) {
            if (ast_1.ClassDeclaration.is(node) && node.isStruct && node.isData) {
                let [fields, naturalSize] = getFieldInfos(node, scopes, ancestors, 0);
                let actualFields = assignActualFields(fields);
                //  calculate stride for each actual field to move to the next element
                //  we have to pad each structure so that it ends at an even
                //  location to the largest contained type
                let largestTypeBytes = 0;
                for (let field of actualFields) {
                    largestTypeBytes = Math.max(fieldTypes[field.type]);
                }
                //  we pad the size if needed so it ends at an even unit same as largest type size
                let paddedSize = Math.ceil(naturalSize / largestTypeBytes) * largestTypeBytes;
                for (let field of actualFields) {
                    field.stride = paddedSize / fieldTypes[field.type];
                }
                // calculate stride for each actual field
                // console.log("==========> ", JSON.stringify(fields, null, 2), actualFields)
                console.log({ largestTypeBytes, naturalSize, paddedSize, actualFields });
                return node.patch({
                    static: [...node.static, createTypedArrayDeclaration(node, fields, actualFields, paddedSize, options)]
                });
            }
        }
    });
}
exports.default = addTypedStructArrays;
