
import * as base from './base.js';

const torch = {};

torch.ModelFactory = class {

    match(context) {
        const reader = torch.T7Reader.open(context);
        if (reader) {
            context.type = 'torch';
            context.target = reader;
        }
    }

    async open(context) {
        const metadata = await context.metadata('torch-metadata.json');
        const reader = context.target;
        reader.callback = (name) => {
            if (name && name !== 'nn.JointTrainModule' && !name.startsWith('nn.MSDNet_') && !name.startsWith('onmt.')) {
                context.error(new torch.Error(`Unsupported type '${name}'.`));
            }
            return null;
        };
        const obj = reader.read();
        let graphs = [];
        if (obj && Array.isArray(obj) && obj.length >= 2 &&
            obj.slice(0, obj.length - 1).every((item) => item.__class__) &&
            !obj[obj.length - 1].__class__) {
            graphs = obj.slice(0, obj.length - 1);
        } else {
            graphs = [obj];
        }
        return new torch.Model(metadata, graphs);
    }
};

torch.Model = class {

    constructor(metadata, graphs) {
        this.format = 'Torch v7';
        this.graphs = graphs.map((graph, index) => new torch.Graph(metadata, index.toString(), graph));
    }
};

torch.Graph = class {

    constructor(metadata, name, module) {
        this.name = name;
        this.inputs = [];
        this.outputs = [];
        this.nodes = [];
        this.groups = 'false';
        const values = new Map();
        values.map = (name, type, tensor) => {
            if (name.length === 0 && tensor) {
                return new torch.Value(name, type || null, tensor || null);
            }
            if (!values.has(name)) {
                values.set(name, new torch.Value(name, type || null, tensor || null));
            } else if (type || tensor) {
                throw new torch.Error(`Duplicate value '${name}'.`);
            }
            return values.get(name);
        };
        const node = new torch.Node(metadata, module, '', values);
        this.nodes.push(node);
    }
};

torch.Argument = class {

    constructor(name, value, type, visible) {
        this.name = name;
        this.value = value;
        this.type = type || null;
        this.visible = visible !== false;
    }
};

torch.Value = class {

    constructor(name, type, initializer) {
        if (typeof name !== 'string') {
            throw new torch.Error(`Invalid value identifier '${JSON.stringify(name)}'.`);
        }
        this.name = name;
        this.type = initializer ? initializer.type : type;
        this.initializer = initializer;
    }
};

torch.Node = class {

    constructor(metadata, module, name, values) {
        this.name = name;
        this.inputs = [];
        this.outputs = [];
        const type = module.__class__ ? `${module.__class__.__module__}.${module.__class__.__name__}` : 'nn.Module';
        this.type = metadata.type(type);
        for (const [key, obj] of Object.entries(module)) {
            if (obj && obj.__class__ && obj.__class__.__module__ === 'torch' && obj.__class__.__name__.endsWith('Storage')) {
                module[key] = obj.data();
            }
        }
        delete module.iSize;
        delete module.finput;
        delete module.fgradInput;
        delete module.output;
        delete module.gradInput;
        delete module.gradWeight;
        delete module.gradBias;
        delete module.grad_tmp;
        delete module.scaleT;
        delete module._input;
        delete module._output;
        delete module._gradInput;
        delete module._gradOutput;
        delete module.buffer;
        delete module.buffer2;
        delete module.tmp_in;
        delete module.tmp_out;
        delete module.accUpdateGradParameters;
        this.attributes = [];
        for (const [name, obj] of Object.entries(module)) {
            if (name === '_type') {
                continue;
            }
            if (obj.__class__ && obj.__class__.__module__ === 'torch' && obj.__class__.__name__.endsWith('Tensor')) {
                const argument = new torch.Argument(name, [values.map('', null, new torch.Tensor(obj))]);
                this.inputs.push(argument);
            } else if (Array.isArray(obj) && obj.every((item) => item && item.__class__)) {
                const nodes = obj.map((module) => new torch.Node(metadata, module, '', values));
                const argument = new torch.Argument(name, nodes, 'object[]');
                this.inputs.push(argument);
            } else if ((Array.isArray(obj) && obj.every((obj) => typeof obj === 'number' || typeof obj === 'string' && typeof obj === 'boolean')) ||
                typeof obj === 'number' || typeof obj === 'string' || typeof obj === 'boolean') {
                let visible = name === 'train' ? false : true;
                const schema = metadata.attribute(type, name);
                if (schema) {
                    if (schema.visible === false) {
                        visible = false;
                    } else if (schema.default !== undefined && Object.prototype.hasOwnProperty.call(schema, 'default')) {
                        visible = false;
                    }
                }
                const attribute = new torch.Argument(name, obj, 'attribute', visible);
                this.inputs.push(attribute);
            } else if (obj) {
                const node = new torch.Node(metadata, obj, '', values);
                const argument = new torch.Argument(name, node, 'object');
                this.inputs.push(argument);
            } else {
                throw new torch.Error(`Invalid input value '${name}'.`);
            }
        }
    }

    _updateSize(module, name) {
        if (Object.prototype.hasOwnProperty.call(module, `${name}W`) &&
            Object.prototype.hasOwnProperty.call(module, `${name}H`)) {
            module[name] = [module[`${name}W`], module[`${name}H`]];
            delete module[`${name}W`];
            delete module[`${name}H`];
        }
    }

    _updateBox(module, name) {
        if (Object.prototype.hasOwnProperty.call(module, `${name}_t`) &&
            Object.prototype.hasOwnProperty.call(module, `${name}_r`) &&
            Object.prototype.hasOwnProperty.call(module, `${name}_b`) &&
            Object.prototype.hasOwnProperty.call(module, `${name}_l`)) {
            module[name] = [module[`${name}_t`], module[`${name}_r`], module[`${name}_b`], module[`${name}_l`]];
            delete module[`${name}_t`];
            delete module[`${name}_r`];
            delete module[`${name}_b`];
            delete module[`${name}_l`];
        }
    }
};

torch.Tensor = class {

    constructor(tensor) {
        this.type = new torch.TensorType(tensor);
        this.encoding = '|';
        this._storage = tensor.storage;
        this._offset = tensor.storage_offset;
    }

    get values() {
        if (this.type.shape.dimensions.length === 0) {
            return [];
        }
        if (this._storage) {
            const data = this._storage.data();
            if (data) {
                const size = this.type.shape.dimensions.reduce((a, b) => a * Number(b), 1);
                return data.slice(this._offset, this._offset + size);
            }
        }
        return null;
    }
};

torch.TensorType = class {

    constructor(tensor) {
        this.dataType = tensor.dataType;
        this.shape = new torch.TensorShape(tensor.size);
    }

    toString() {
        return (this.dataType || '?') + this.shape.toString();
    }
};

torch.TensorShape = class {

    constructor(dimensions) {
        this.dimensions = dimensions;
    }

    toString() {
        if (this.dimensions) {
            if (this.dimensions.length === 0) {
                return '';
            }
            return `[${this.dimensions.map((dimension) => dimension.toString()).join(',')}]`;
        }
        return '';
    }
};

torch.T7Reader = class {

    static open(context) {
        const stream = context.stream;
        if (stream && stream.length >= 4 && stream.peek(4).every((value, index) => value === 0x00 || (index === 0 && value <= 0x08))) {
            const reader = new torch.BinaryReader(stream);
            return new torch.T7Reader(reader);
        }
        if (stream && stream.length >= 2) {
            const buffer = stream.peek(2);
            const value = String.fromCharCode(stream.peek(1)[0]);
            if (buffer[1] === 0x0a && (value >= '0' && value <= '8')) {
                const reader = new torch.TextReader(stream);
                return new torch.T7Reader(reader);
            }
        }
        return null;
    }

    constructor(reader) {
        // https://github.com/torch/torch7
        // https://github.com/torch/nngraph
        this._reader = reader;
        this._memo = new Map();
        this._types = new Map();
        const Storage = class {
            constructor(dataType, itemSize) {
                this.dataType = dataType;
                this.itemSize = itemSize;
            }
            read(reader) {
                this.size = reader.int64();
                this.reader = reader.storage(this.size, this.itemSize, this.dataType);
            }
            data() {
                if (this.reader) {
                    const reader = this.reader;
                    reader.seek(0);
                    const dataType = this.dataType;
                    const size = this.size;
                    const array = new Array(size);
                    for (let i = 0; i < size; i++) {
                        switch (dataType) {
                            case 'uint8':
                                array[i] = reader.byte();
                                break;
                            case 'int8':
                                array[i] = reader.int8();
                                break;
                            case 'int16':
                                array[i] = reader.int16();
                                break;
                            case 'int32':
                                array[i] = reader.int32();
                                break;
                            case 'int64':
                                array[i] = reader.int64();
                                break;
                            case 'float32':
                                array[i] = reader.float32();
                                break;
                            case 'float64':
                                array[i] = reader.float64();
                                break;
                            default:
                                throw new torch.Error(`Unsupported data type '${dataType}'.`);
                        }
                    }
                    this._data = array;
                    delete this.reader;
                }
                return this._data;
            }
        };
        const Tensor = class {
            constructor(dataType) {
                this.dataType = dataType;
            }
            read(reader) {
                const dim = reader.int32();
                this.size = reader.int64s(dim);
                this.stride = reader.int64s(dim);
                this.storage_offset = reader.int64() - 1;
                this.storage = reader.read();
            }
        };
        this.register('bnn.Binary');
        this.register('bnn.SpatialConvolution');
        this.register('cudnn.BatchNormalization');
        this.register('cudnn.BatchBRNNReLU');
        this.register('cudnn.BLSTM');
        this.register('cudnn.ReLU');
        this.register('cudnn.RNN');
        this.register('cudnn.Sigmoid');
        this.register('cudnn.SoftMax');
        this.register('cudnn.LogSoftMax');
        this.register('cudnn.normal3DConv');
        this.register('cudnn.normal3DdeConv');
        this.register('cudnn.SpatialAveragePooling');
        this.register('cudnn.SpatialBatchNormalization');
        this.register('cudnn.SpatialConvolution');
        this.register('cudnn.SpatialFullConvolution');
        this.register('cudnn.SpatialMaxPooling');
        this.register('cudnn.SpatialSoftMax');
        this.register('cudnn.Tanh');
        this.register('cudnn.VolumetricAveragePooling');
        this.register('cudnn.VolumetricBatchNormalization');
        this.register('cudnn.VolumetricConvolution');
        this.register('cudnn.VolumetricMaxPooling');
        this.register('Dict');
        this.register('inn.ConstAffine');
        this.register('inn.SpatialMaxPooling');
        this.register('nn.Abs');
        this.register('nn.AddConstant');
        this.register('nn.BatchNormalization');
        this.register('nn.BilinearSamplerBHWD');
        this.register('nn.BinActiveZ'); // allenai/XNOR-Net
        this.register('nn.BCECriterion');
        this.register('nn.Bottle');
        this.register('nn.Clamp');
        this.register('nn.CMul');
        this.register('nn.CAddTable');
        this.register('nn.CDivTable');
        this.register('nn.CMulTable');
        this.register('nn.CSubTable');
        this.register('nn.Concat');
        this.register('nn.Copy');
        this.register('nn.ConcatTable');
        this.register('nn.Contiguous');
        this.register('nn.Constant');
        this.register('nn.CostVolMulti');
        this.register('nn.DataParallelTable');
        this.register('nn.DepthConcat');
        this.register('nn.Dropout');
        this.register('nn.Exp');
        this.register('nn.ExpOut');
        this.register('nn.FlattenTable');
        this.register('nn.GenNoise');
        this.register('nn.Identity');
        this.register('nn.Index');
        this.register('nn.Inception');
        this.register('nn.InstanceNormalization');
        this.register('nn.JoinTable');
        this.register('nn.JointTrain');
        this.register('nn.KeypointCoordinate');
        this.register('nn.LeakyReLU');
        this.register('nn.Linear');
        this.register('nn.LinearNoBias');
        this.register('nn.LogSoftMax');
        this.register('nn.LookupTable');
        this.register('nn.LSTM');
        this.register('nn.MaskZero');
        this.register('nn.MapTable');
        this.register('nn.Max');
        this.register('nn.Mean');
        this.register('nn.Min');
        this.register('nn.MulConstant');
        this.register('nn.MM');
        this.register('nn.MSECriterion');
        this.register('nn.Narrow');
        this.register('nn.NarrowTable');
        this.register('nn.Normalize');
        this.register('nn.Normalize2');
        this.register('nn.NoiseFill');
        this.register('nn.Padding');
        this.register('nn.Parallel');
        this.register('nn.ParallelCriterion');
        this.register('nn.ParallelTable');
        this.register('nn.PixelShuffle');
        this.register('nn.Power');
        this.register('nn.PReLU');
        this.register('nn.Recursor');
        this.register('nn.ReLU');
        this.register('nn.Replicate');
        this.register('nn.Reshape');
        this.register('nn.ShaveImage');
        this.register('nn.Select');
        this.register('nn.SelectTable');
        this.register('nn.Sequencer');
        this.register('nn.Sequential');
        this.register('nn.Sigmoid');
        this.register('nn.Sum');
        this.register('nn.SoftMax');
        this.register('nn.SpatialAveragePooling');
        this.register('nn.SpatialBatchNormalization');
        this.register('nn.SpatialConvolution');
        this.register('nn.SpatialConvolution1_fw');
        this.register('nn.SpatialConvolutionMM');
        this.register('nn.SpatialCrossMapLRN');
        this.register('nn.SpatialDilatedConvolution');
        this.register('nn.SpatialDropout');
        this.register('nn.SpatialFractionalMaxPooling');
        this.register('nn.SpatialFullConvolution');
        this.register('nn.SpatialLPPooling');
        this.register('nn.SpatialMaxPooling');
        this.register('nn.SpatialMaxUnpooling');
        this.register('nn.SpatialReflectionPadding');
        this.register('nn.SpatialReplicationPadding');
        this.register('nn.SpatialSoftMax');
        this.register('nn.SpatialSubtractiveNormalization');
        this.register('nn.SpatialUpSamplingBilinear');
        this.register('nn.SpatialUpSamplingNearest');
        this.register('nn.SpatialZeroPadding');
        this.register('nn.SplitTable');
        this.register('nn.Squeeze');
        this.register('nn.Square');
        this.register('nn.Sqrt');
        this.register('nn.StereoJoin');
        this.register('nn.Tanh');
        this.register('nn.Transpose');
        this.register('nn.TotalVariation');
        this.register('nn.Unpool');
        this.register('nn.View');
        this.register('nn.gModule');
        this.register('nngraph.Node');
        this.register('graph.Edge');
        this.register('graph.Graph');
        this.register('torch.ByteTensor', class extends Tensor {
            constructor() {
                super('uint8');
            }
        });
        this.register('torch.CharTensor', class extends Tensor {
            constructor() {
                super('int8');
            }
        });
        this.register('torch.ShortTensor', class extends Tensor {
            constructor() {
                super('int16');
            }
        });
        this.register('torch.IntTensor', class extends Tensor {
            constructor() {
                super('int32');
            }
        });
        this.register('torch.LongTensor', class extends Tensor {
            constructor() {
                super('int64');
            }
        });
        this.register('torch.FloatTensor', class extends Tensor {
            constructor() {
                super('float32');
            }
        });
        this.register('torch.DoubleTensor', class extends Tensor {
            constructor() {
                super('float64');
            }
        });
        this.register('torch.CudaByteTensor', class extends Tensor {
            constructor() {
                super('uint8');
            }
        });
        this.register('torch.CudaCharTensor', class extends Tensor {
            constructor() {
                super('int8');
            }
        });
        this.register('torch.CudaShortTensor', class extends Tensor {
            constructor() {
                super('int16');
            }
        });
        this.register('torch.CudaIntTensor', class extends Tensor {
            constructor() {
                super('int32');
            }
        });
        this.register('torch.CudaLongTensor', class extends Tensor {
            constructor() {
                super('int64');
            }
        });
        this.register('torch.CudaTensor', class extends Tensor {
            constructor() {
                super('float32');
            }
        });
        this.register('torch.CudaDoubleTensor', class extends Tensor {
            constructor() {
                super('float64');
            }
        });
        this.register('torch.ByteStorage', class extends Storage {
            constructor() {
                super('uint8', 1);
            }
        });
        this.register('torch.CharStorage', class extends Storage {
            constructor() {
                super('int8', 1);
            }
        });
        this.register('torch.ShortStorage', class extends Storage {
            constructor() {
                super('int16', 2);
            }
        });
        this.register('torch.IntStorage', class extends Storage {
            constructor() {
                super('int32', 4);
            }
        });
        this.register('torch.LongStorage', class extends Storage {
            constructor() {
                super('int64', 8);
            }
        });
        this.register('torch.FloatStorage', class extends Storage {
            constructor() {
                super('float32', 4);
            }
        });
        this.register('torch.DoubleStorage', class extends Storage {
            constructor() {
                super('float64', 8);
            }
        });
        this.register('torch.CudaByteStorage', class extends Storage {
            constructor() {
                super('uint8', 1);
            }
        });
        this.register('torch.CudaCharStorage', class extends Storage {
            constructor() {
                super('int8', 1);
            }
        });
        this.register('torch.CudaShortStorage', class extends Storage {
            constructor() {
                super('int16', 2);
            }
        });
        this.register('torch.CudaIntStorage', class extends Storage {
            constructor() {
                super('int32', 4);
            }
        });
        this.register('torch.CudaLongStorage', class extends Storage {
            constructor() {
                super('int64', 8);
            }
        });
        this.register('torch.CudaIntStorage', class extends Storage {
            constructor() {
                super('int32', 4);
            }
        });
        this.register('torch.CudaStorage', class extends Storage {
            constructor() {
                super('float32', 4);
            }
        });
        this.register('torch.CudaFloatStorage', class extends Storage {
            constructor() {
                super('float64', 8);
            }
        });
        this.register('w2nn.AuxiliaryLossTable');
        this.register('w2nn.InplaceClip01');
        this.register('w2nn.ScaleTable');
        this.register('LuaFunction', class {
            constructor(size, dumped, upvalues) {
                this.size = size;
                this.dumped = dumped;
                this.upvalues = upvalues;
            }
        });
    }

    register(name, type) {
        type = type || class {};
        const parts = name.split('.');
        type.__name__ = parts.pop();
        type.__module__ = parts.join('.');
        type.prototype.__class__ = type;
        this._types.set(name, type);
    }

    read() {
        const type = this.int32();
        switch (type) {
            case 0: return null;
            case 1: return this.float64();
            case 2: return this.string();
            case 3: return this.table();
            case 4: return this.object();
            case 5: return this.boolean();
            case 6: return this.function();
            case 7: return this.function();
            case 8: return this.function();
            default: throw new torch.Error(`File format has invalid type '${type}'.`);
        }
    }

    boolean() {
        return this._reader.boolean();
    }

    int32() {
        return this._reader.int32();
    }

    int64() {
        return this._reader.int64();
    }

    int64s(size) {
        return this._reader.int64s(size);
    }

    float64() {
        return this._reader.float64();
    }

    string() {
        return this._reader.string();
    }

    object() {
        const index = this.int32();
        if (this._memo.has(index)) {
            return this._memo.get(index);
        }
        let version = this.string();
        let name = null;
        if (version.startsWith('V ')) {
            name = this.string();
            version = parseInt(version.split(' ')[1], 10);
        } else {
            name = version;
            version = 0;
        }
        if (!this._types.has(name)) {
            this.callback(name);
            this.register(name);
        }
        const type = this._types.get(name);
        const obj = Reflect.construct(type, []);
        this._memo.set(index, obj);
        if (obj.read) {
            obj.read(this, version);
        } else {
            const attributes = this.read();
            if (attributes !== null) {
                for (const [key, value] of Array.from(attributes)) {
                    obj[key] = value;
                }
            }
        }
        return obj;
    }

    table() {
        const index = this.int32();
        if (this._memo.has(index)) {
            return this._memo.get(index);
        }
        const table = new Map();
        this._memo.set(index, table);
        const size = this.int32();
        let convert = true;
        let sum = 0;
        for (let i = 0; i < size; i++) {
            const key = this.read();
            const value = this.read();
            table.set(key, value);
            if (Number.isInteger(key) && key >= 0) {
                sum += key;
            } else {
                convert = false;
            }
        }
        const n = table.size;
        if (convert && (n * (n + 1)) === (2 * sum)) {
            const list = [];
            for (let i = 0; i < n; i++) {
                let item = table.get(i + 1);
                if (item === table) {
                    item = list;
                }
                list.push(item);
            }
            this._memo.set(index, list);
            return list;
        }
        return table;
    }

    function() {
        const index = this.int32();
        if (this._memo.has(index)) {
            return this._memo.get(index);
        }
        const size = this.int32();
        const dumped = this._reader.read(size);
        const upvalues = this.read();
        const type = this._types.get('LuaFunction');
        const obj = Reflect.construct(type, [size, dumped, upvalues]);
        this._memo.set(index, obj);
        return obj;
    }

    storage(size, itemSize, dataType) {
        return this._reader.storage(size, itemSize, dataType);
    }
};

torch.BinaryReader = class {

    constructor(data) {
        this._reader = base.BinaryReader.open(data);
        this._textDecoder = new TextDecoder('ascii');
    }

    seek(position) {
        this._reader.seek(position);
    }

    skip(offset) {
        this._reader.skip(offset);
    }

    read(length) {
        return this._reader.read(length);
    }

    boolean() {
        return this.int32() === 1;
    }

    int32() {
        return this._reader.int32();
    }

    int64() {
        return this._reader.int64().toNumber();
    }

    int64s(size) {
        const array = [];
        for (let i = 0; i < size; i++) {
            array.push(this.int64());
        }
        return array;
    }

    float32() {
        return this._reader.float32();
    }

    float64() {
        return this._reader.float64();
    }

    string() {
        const size = this.int32();
        const buffer = this.read(size);
        return this._textDecoder.decode(buffer);
    }

    storage(size, itemSize) {
        const buffer = this.read(size * itemSize);
        return new torch.BinaryReader(buffer);
    }
};

torch.TextReader = class {

    constructor(data, separator) {
        this._buffer = data instanceof Uint8Array ? data : data.peek();
        this._position = 0;
        this._dataView = new DataView(this._buffer.buffer, this._buffer.byteOffset, this._buffer.byteLength);
        this._textDecoder = new TextDecoder('ascii');
        this._separator = separator || 0x0a;
    }

    seek(position) {
        this._position = position;
    }

    line(size) {
        const start = this._position;
        while (this._position < this._buffer.length && size > -1) {
            const c = this._buffer[this._position++];
            if (c === this._separator) {
                return this._buffer.slice(start, this._position - 1);
            } else if (this._position === this._buffer.length) {
                return this._buffer.slice(start, this._position);
            }
            size--;
        }
        throw new torch.Error('Line exceeded maximum length.');
    }

    boolean() {
        return this.int32() === 1;
    }

    read(size) {
        return this.line(size);
    }

    int8() {
        return this.int64();
    }

    int16() {
        return this.int64();
    }

    int32() {
        return this.int64();
    }

    int64() {
        const token = this._textDecoder.decode(this.line(20));
        const number = Number.parseInt(token, 10);
        if (Number.isNaN(token - number)) {
            throw new torch.Error(`Couldn't parse int64 '${token}'.`);
        }
        return number;
    }

    int64s(size) {
        const array = [];
        if (size > 0) {
            const content = this._textDecoder.decode(this.line(Number.MAX_SAFE_INTEGER));
            for (const token of content.split(' ')) {
                const number = Number.parseInt(token, 10);
                if (Number.isNaN(token - number)) {
                    throw new torch.Error(`Couldn't parse int64 '${token}'.`);
                }
                array.push(number);
            }
        }
        return array;
    }

    float32() {
        return this.float64();
    }

    float64() {
        const token = this._textDecoder.decode(this.line(24));
        if (token.startsWith('-nan')) {
            return -NaN;
        }
        if (token.startsWith('nan')) {
            return NaN;
        }
        if (token.startsWith('inf')) {
            return Infinity;
        }
        if (token.startsWith('-inf')) {
            return -Infinity;
        }
        const number = Number.parseFloat(token);
        if (Number.isNaN(token - number)) {
            throw new torch.Error(`Couldn't parse float '${token}'.`);
        }
        return number;
    }

    string() {
        const size = this.int32();
        if (size === 0) {
            return '';
        }
        const data = this.line(size);
        const content = this._textDecoder.decode(data);
        if (size !== content.length) {
            throw new torch.Error('Invalid string length.');
        }
        return content;
    }

    storage(size, itemSize, dataType) {
        if (size <= 0) {
            throw new torch.Error(`Unsupported storage size '${size}'.`);
        }
        if (dataType === 'uint8') {
            const start = this._position;
            this._position += size;
            const bytes = this._buffer.slice(start, this._position);
            this.line(0);
            return new torch.BinaryReader(bytes);
        }
        const data = this.line(Number.MAX_SAFE_INTEGER);
        return new torch.TextReader(data, 0x20);
    }
};

torch.Error = class extends Error {

    constructor(message) {
        super(message);
        this.name = 'Error loading Torch model.';
    }
};

export const ModelFactory = torch.ModelFactory;
