"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.resetMapping = exports.saveMapping = exports.getAllMappings = exports.readyKeyboardControllers = exports.defaultKeyboard = exports.axisModifier = exports.collapsePorts = exports.tickConnections = exports.processConnection = exports.checkConnections = exports.addController = exports.pollEvent = exports.keyboardGamepads = exports.Controller = exports.resetConnected = exports.deserializeFrame = exports.prettyFrame = exports.serializeFrame = exports.frameSize = exports.base64Frame = exports.InputFrame = exports.NetBuffer = exports.ReplayBuffer = exports.Replay = exports.connectAdapter = exports.pollUSB = exports.disconnecting = exports.layouts = exports.axisList = exports.buttonList = exports.isKeyboardConnected = exports.unconnected = exports.connected = exports.connecting = exports.waiting = exports.inputCapacitor = exports.framesEqual = void 0;
const JSONC = require("jsonc-parser");
const capacitor_1 = require("@bluehexagons/capacitor");
const engine_1 = require("./engine");
const math_1 = require("./math");
const networking_1 = require("./networking");
const rendering_1 = require("./rendering");
const terminal_1 = require("./terminal");
const utils_1 = require("./utils");
const framesEqual = (left, right) => left.hmove === right.hmove
    && left.vmove === right.vmove
    && left.hright === right.hright
    && left.vright === right.vright
    && left.shield1 === right.shield1
    && left.shield2 === right.shield2
    && left.attack === right.attack
    && left.jump === right.jump
    && left.special === right.special
    && left.grab === right.grab
    && left.shield1Hard === right.shield1Hard
    && left.shield2Hard === right.shield2Hard
    && left.dup === right.dup
    && left.dright === right.dright
    && left.ddown === right.ddown
    && left.dleft === right.dleft
    && left.select === right.select
    && left.start === right.start;
exports.framesEqual = framesEqual;
exports.inputCapacitor = new capacitor_1.Capacitor(exports.framesEqual);
exports.waiting = [];
exports.connecting = [];
exports.connected = [];
exports.unconnected = [];
let keyboardConnected = false;
const isKeyboardConnected = () => keyboardConnected;
exports.isKeyboardConnected = isKeyboardConnected;
const snapDeg = 16;
const deadZone = 0.25 ** 2;
const press = {};
const last = {};
exports.buttonList = [
    'attack',
    'jump',
    'special',
    'grab',
    'shield1',
    'shield2',
    'shield1Hard',
    'shield2Hard',
    'select',
    'start',
    'dup',
    'ddown',
    'dleft',
    'dright',
];
exports.axisList = [
    'hmove',
    'vmove',
    'hright',
    'vright',
    'shield1',
    'shield2',
];
exports.buttonList.forEach((name) => {
    press[name] = name + 'Press';
    last[name] = name + 'Last';
});
const nameToMapping = [
    {
        pattern: /Vendor: 1a34 Product: f705|1a34-f705-HuiJia/,
        mapping: 'HuiJia_gamecube',
    },
];
exports.layouts = {
    standard: {
        name: 'standard',
        buttons: [
            'attack',
            'jump',
            'special',
            'jump',
            '',
            '',
            'start',
            '',
            '',
            'grab',
            'grab',
            'dup',
            'ddown',
            'dleft',
            'dright',
        ],
        buttonNames: [
            'A',
            'B',
            'X',
            'Y',
            'Select',
            'System',
            'Start',
            'Left-Stick',
            'Right-Stick',
            'ZL',
            'ZR',
            'Up',
            'Down',
            'Left',
            'Right',
        ],
        axes: ['hmove', 'vmove', 'hright', 'vright', 'shield1', 'shield2'],
        axisNames: [
            'Left-H',
            'Left-V',
            'Right-H',
            'Right-V',
            'Left-Trigger',
            'Right-Trigger',
        ],
        digitalShield: false,
    },
    keyboard: {
        name: 'keyboard',
        buttons: exports.buttonList,
        axes: exports.axisList,
        digitalShield: true,
    },
    gcn_native: {
        name: 'gcn_native',
        buttons: [
            'attack',
            'special',
            'jump',
            'jump',
            'dleft',
            'dright',
            'ddown',
            'dup',
            'start',
            'grab',
            'shield2Hard',
            'shield1Hard',
        ],
        buttonNames: [
            'A',
            'B',
            'X',
            'Y',
            'Left',
            'Right',
            'Down',
            'Up',
            'Start',
            'Z',
            'Right-Button',
            'Left-Button',
        ],
        axes: ['hmove', 'vmove', 'hright', 'vright', 'shield1', 'shield2'],
        axisNames: [
            'Left-H',
            'Left-V',
            'Right-H',
            'Right-V',
            'Left-Trigger',
            'Right-Trigger',
        ],
        digitalShield: true,
        calibrate: true,
        range: 100,
        shieldRange: 160,
    },
};
const mapGamepad = (kind, id) => {
    if (utils_1.objHas(exports.layouts, kind)) {
        console.log('Layout found for', kind);
        return exports.layouts[kind];
    }
    for (let i = 0; i < nameToMapping.length; i++) {
        if (nameToMapping[i].pattern.test(id)) {
            console.log('Guessed', id, 'as', nameToMapping[i].mapping + '; Layout found');
            return exports.layouts[nameToMapping[i].mapping];
        }
    }
    console.warn('No layout found, using standard', id + ',', kind);
    return exports.layouts.standard;
};
class GCGamepad {
    constructor(port) {
        this.buttons = new Uint8Array(12);
        this.axes = new Float32Array(6);
        this.shieldDeadzone = 0.2;
        this.kind = 'gcn_native';
        this.id = 'gcn';
        this.rawbuttons = 0;
        this.origins = new Uint8Array(6);
        this.type = 0;
        this.port = -1;
        this.connected = false;
        this.controller = null;
        this.pressed = false;
        this.port = port;
        this.id = this.id + (port + 1);
    }
    update(buf) {
        const offset = this.port * 9 + 1;
        const type = buf[offset] >> 4;
        if (type === 0) {
            if (this.type !== 0) {
                console.log('controller ' + this.port + ' disconnected');
            }
            if (this.connected) {
                this.connected = false;
                exports.disconnecting.push(this.controller);
            }
            this.type = type;
            return;
        }
        if (this.type === 0) {
            console.log(`controller ${this.port} connected`, buf[offset], buf[offset + 1]);
            for (let i = 0; i < 6; i++) {
                this.origins[i] = buf[offset + 3 + i];
            }
            if (!this.connected) {
                this.connected = true;
                gcConnecting.push(this);
            }
        }
        this.type = type;
        this.rawbuttons = buf[offset + 1] | (buf[offset + 2] << 8);
        for (let i = 0; i < 12; i++) {
            this.buttons[i] = (this.rawbuttons & (1 << i)) !== 0 ? 1 : 0;
        }
        for (let i = 0; i < 6; i++) {
            this.axes[i] = buf[offset + 3 + i] - this.origins[i];
        }
        this.axes[1] = -this.axes[1];
        this.axes[3] = -this.axes[3];
    }
}
const gcGamepads = [];
const gcConnecting = [];
exports.disconnecting = [];
let rb = Buffer.alloc(37);
const pollUSB = () => {
    for (let i = 0; i < gcGamepads.length; i++) {
        gcGamepads[i].update(rb);
    }
};
exports.pollUSB = pollUSB;
const connectAdapter = (buffer) => {
    rb = buffer;
    const adapterMessages = [];
    for (let i = 0; i < 4; i++) {
        gcGamepads.push(new GCGamepad(i));
    }
    return adapterMessages;
};
exports.connectAdapter = connectAdapter;
class Replay {
    constructor(controller) {
        const entity = controller.hook;
        this.controller = controller;
        this.state = {
            x: entity.x,
            y: entity.y,
            face: entity.face,
            damage: entity.damage,
            stocks: entity.stocks,
        };
        this.buffer = new ReplayBuffer();
    }
    play(frame) {
        const entity = this.controller.hook;
        entity.refresh();
        entity.x = this.state.x;
        entity.y = this.state.y - 0.1;
        entity.face = this.state.face;
        entity.damage = this.state.damage;
        entity.stocks = this.state.stocks;
        entity.airborne = true;
        this.buffer.reset(frame);
    }
    pause() {
    }
}
exports.Replay = Replay;
class ReplayBuffer extends Array {
    constructor() {
        super(...arguments);
        this.index = 0;
        this.pollFrame = 0;
    }
    reset(frame) {
        this.index = 0;
        this.pollFrame = frame;
    }
    next() {
        return this[this.index++];
    }
    insert(_at, frame) {
        this[this.index++] = frame;
    }
    peek() {
        return this[this.index];
    }
    empty() {
        return this.index >= this.length || this[this.index] === null;
    }
    size() {
        return this.length - this.index;
    }
}
exports.ReplayBuffer = ReplayBuffer;
class NetBuffer extends Array {
    constructor(delay, startFrame, netFrame) {
        super(0);
        this.pollFrame = 0;
        this.netFrame = 0;
        this.skipped = 0;
        this.pollFrame = startFrame;
        this.netFrame = netFrame;
        for (let i = 0; i < delay; i++) {
            this.push(new InputFrame());
        }
    }
    reset(frame) {
        this.pollFrame = frame;
    }
    next(frame) {
        const f = frame - this.pollFrame;
        return this[f];
    }
    insert(at, frame) {
        const f = at - this.netFrame;
        if (f > this.length) {
            for (let i = this.length; i < f; i++) {
                this[i] = null;
            }
        }
        else if (f < this.length && this[f] !== null) {
            if (!exports.framesEqual(this[f], frame)) {
                console.log(`** Desync!\n  Controller mismatch, oh no :(\n  at=${at} f=${f}`);
            }
        }
        this[f] = frame;
    }
    peek(frame) {
        return this[frame - this.pollFrame];
    }
    empty(frame) {
        const f = frame - this.pollFrame;
        const empty = f >= this.length || this[f] === null;
        if (empty) {
            this.skipped++;
        }
        else {
            this.skipped = 0;
        }
        return empty;
    }
    size(frame) {
        return this.length - frame + this.pollFrame;
    }
}
exports.NetBuffer = NetBuffer;
class InputFrame {
    constructor() {
        this.attack = 0;
        this.jump = 0;
        this.special = 0;
        this.grab = 0;
        this.shield1 = 0;
        this.shield2 = 0;
        this.shield1Hard = 0;
        this.shield2Hard = 0;
        this.select = 0;
        this.start = 0;
        this.dup = 0;
        this.ddown = 0;
        this.dleft = 0;
        this.dright = 0;
        this.hmove = 0;
        this.vmove = 0;
        this.hright = 0;
        this.vright = 0;
        this.controllerID = 0;
        this.none = 0;
        this[''] = 0;
    }
}
exports.InputFrame = InputFrame;
const base64Frame = (f) => {
    const b = Buffer.allocUnsafe(exports.frameSize);
    exports.serializeFrame(f, b, 0);
    return b.toString('base64');
};
exports.base64Frame = base64Frame;
exports.frameSize = 10;
const serializeFrame = (frame, buffer, offset) => {
    let byte = offset;
    let bit = 0;
    buffer.writeUInt16LE(frame.controllerID, byte);
    byte = byte + 2;
    buffer.writeUInt16LE((frame.attack << bit++)
        | (frame.jump << bit++)
        | (frame.special << bit++)
        | (frame.grab << bit++)
        | (frame.shield1Hard << bit++)
        | (frame.shield2Hard << bit++)
        | (frame.select << bit++)
        | (frame.start << bit++)
        | (frame.dup << bit++)
        | (frame.ddown << bit++)
        | (frame.dleft << bit++)
        | (frame.dright << bit++), byte);
    byte = byte + 2;
    buffer.writeInt8(math_1.ftob(frame.shield1), byte);
    byte = byte + 1;
    buffer.writeInt8(math_1.ftob(frame.shield2), byte);
    byte = byte + 1;
    buffer.writeInt8(math_1.ftob(frame.hmove), byte);
    byte = byte + 1;
    buffer.writeInt8(math_1.ftob(frame.vmove), byte);
    byte = byte + 1;
    buffer.writeInt8(math_1.ftob(frame.hright), byte);
    byte = byte + 1;
    buffer.writeInt8(math_1.ftob(frame.vright), byte);
};
exports.serializeFrame = serializeFrame;
const prettyFrame = (frame, ticks) => [
    `${ticks.toFixed(0).padStart(6, ' ')}F`,
    `${networking_1.getNetFrame(ticks).toFixed(0).padStart(6, ' ')}N`,
    `P${frame.controllerID}`,
    `h${frame.hmove.toFixed(3)}`,
    `v${frame.vmove.toFixed(3)}`,
    `a${frame.attack}`,
    `b${frame.special}`,
].join(' ');
exports.prettyFrame = prettyFrame;
const deserializeFrame = (buffer, offset) => {
    const frame = new InputFrame();
    let byte = 2;
    const buttons = buffer.readUInt16LE(offset + byte);
    let bit = 0;
    byte = byte + 2;
    frame.controllerID = buffer.readUInt16LE(offset);
    frame.attack = (buttons >>> bit++) & 1;
    frame.jump = (buttons >>> bit++) & 1;
    frame.special = (buttons >>> bit++) & 1;
    frame.grab = (buttons >>> bit++) & 1;
    frame.shield1Hard = (buttons >>> bit++) & 1;
    frame.shield2Hard = (buttons >>> bit++) & 1;
    frame.select = (buttons >>> bit++) & 1;
    frame.start = (buttons >>> bit++) & 1;
    frame.dup = (buttons >>> bit++) & 1;
    frame.ddown = (buttons >>> bit++) & 1;
    frame.dleft = (buttons >>> bit++) & 1;
    frame.dright = (buttons >>> bit++) & 1;
    frame.shield1 = math_1.btof(buffer.readInt8(offset + byte));
    byte = byte + 1;
    frame.shield2 = math_1.btof(buffer.readInt8(offset + byte));
    byte = byte + 1;
    frame.hmove = math_1.btof(buffer.readInt8(offset + byte));
    byte = byte + 1;
    frame.vmove = math_1.btof(buffer.readInt8(offset + byte));
    byte = byte + 1;
    frame.hright = math_1.btof(buffer.readInt8(offset + byte));
    byte = byte + 1;
    frame.vright = math_1.btof(buffer.readInt8(offset + byte));
    return frame;
};
exports.deserializeFrame = deserializeFrame;
const resetConnected = () => {
    exports.inputCapacitor.clear();
    for (const c of exports.connected) {
        const client = exports.inputCapacitor.connect({ sizeOffset: rendering_1.getPollTicks() });
        c.reset();
        c.client = client;
        console.log(`Reconnected controller ${c.name}`);
    }
};
exports.resetConnected = resetConnected;
let controllerID = 0;
class Controller {
    constructor(gamepad) {
        this.controllerID = 0;
        this.portNumber = -1;
        this.gamepad = null;
        this.mapping = 'default';
        this.hook = null;
        this.character = null;
        this.color = null;
        this.seed = null;
        this.style = 0;
        this.team = 0;
        this.name = null;
        this.tag = null;
        this.buttonMap = null;
        this.axisMap = null;
        this.digitalShield = true;
        this.digitalShieldThreshold = 0.95;
        this.calibrate = false;
        this.debugging = false;
        this.noTapJump = false;
        this.layout = exports.layouts.standard;
        this.peer = null;
        this.polling = true;
        this.frame = new InputFrame();
        this.frames = [];
        this.client = null;
        this.replay = null;
        this.attack = 0;
        this.attackPress = false;
        this.attackLast = false;
        this.jump = 0;
        this.jumpPress = false;
        this.jumpLast = false;
        this.special = 0;
        this.specialPress = false;
        this.specialLast = false;
        this.grab = 0;
        this.grabPress = false;
        this.grabLast = false;
        this.shield = 0;
        this.shieldPress = false;
        this.shieldHard = 0;
        this.shieldHardPress = false;
        this.shield1 = 0;
        this.shield1Press = false;
        this.shield1Last = false;
        this.shield1Hard = 0;
        this.shield1HardPress = false;
        this.shield2 = 0;
        this.shield2Press = false;
        this.shield2Last = false;
        this.shield2Hard = 0;
        this.shield2HardPress = false;
        this.select = 0;
        this.selectPress = false;
        this.selectLast = false;
        this.start = 0;
        this.startPress = false;
        this.startLast = false;
        this.connect = 0;
        this.connectPress = false;
        this.connectLast = false;
        this.dup = 0;
        this.dupPress = false;
        this.dupLast = false;
        this.ddown = 0;
        this.ddownPress = false;
        this.ddownLast = false;
        this.dleft = 0;
        this.dleftPress = false;
        this.dleftLast = false;
        this.dright = 0;
        this.drightPress = false;
        this.drightLast = false;
        this.rleft = 0;
        this.rright = 0;
        this.rup = 0;
        this.rdown = 0;
        this.hmoveLast = 0;
        this.hmove = 0;
        this.vmoveLast = 0;
        this.vmove = 0;
        this.hright = 0;
        this.vright = 0;
        this.reversible = 0;
        this.left = 0;
        this.leftTap = 120;
        this.leftUntap = 120;
        this.right = 0;
        this.rightTap = 120;
        this.rightUntap = 120;
        this.up = 0;
        this.upTap = 120;
        this.upUntap = 120;
        this.down = 0;
        this.downTap = 120;
        this.downUntap = 120;
        this.calibration = {
            hmovemin: -1,
            hmovemax: 1,
            vmovemin: -1,
            vmovemax: 1,
            hrightmin: -1,
            hrightmax: 1,
            vrightmin: -1,
            vrightmax: 1,
            shield1min: 0,
            shield1max: 1,
            shield2min: 0,
            shield2max: 1,
        };
        this.axisRange = 1;
        this.shieldRange = 1;
        if (!gamepad) {
            return;
        }
        const layout = mapGamepad(gamepad.kind, gamepad.id);
        this.name = gamepad.id;
        this.controllerID = controllerID++;
        this.gamepad = gamepad;
        this.layout = layout;
        this.buttonMap = [...layout.buttons];
        this.axisMap = [...layout.axes];
        this.resetMapping();
        this.reloadMapping();
        this.digitalShield = layout.digitalShield;
        if (utils_1.objHas(layout, 'calibrate') && layout.calibrate) {
            this.calibrate = true;
            const calibration = this.calibration;
            if (utils_1.objHas(layout, 'range')) {
                this.axisRange = layout.range;
                calibration.hmovemin = -this.axisRange;
                calibration.hmovemax = this.axisRange;
                calibration.vmovemin = -this.axisRange;
                calibration.vmovemax = this.axisRange;
                calibration.hrightmin = -this.axisRange;
                calibration.hrightmax = this.axisRange;
                calibration.vrightmin = -this.axisRange;
                calibration.vrightmax = this.axisRange;
                calibration.shield1max = this.axisRange;
                calibration.shield2max = this.axisRange;
            }
            if (utils_1.objHas(layout, 'shieldRange')) {
                this.shieldRange = layout.shieldRange;
                calibration.shield1max = this.shieldRange;
                calibration.shield2max = this.shieldRange;
            }
        }
    }
    reset() {
        this.attack = 0;
        this.attackPress = false;
        this.attackLast = false;
        this.jump = 0;
        this.jumpPress = false;
        this.jumpLast = false;
        this.special = 0;
        this.specialPress = false;
        this.specialLast = false;
        this.grab = 0;
        this.grabPress = false;
        this.grabLast = false;
        this.shield = 0;
        this.shieldPress = false;
        this.shieldHard = 0;
        this.shieldHardPress = false;
        this.shield1 = 0;
        this.shield1Press = false;
        this.shield1Last = false;
        this.shield1Hard = 0;
        this.shield1HardPress = false;
        this.shield2 = 0;
        this.shield2Press = false;
        this.shield2Last = false;
        this.shield2Hard = 0;
        this.shield2HardPress = false;
        this.select = 0;
        this.selectPress = false;
        this.selectLast = false;
        this.start = 0;
        this.startPress = false;
        this.startLast = false;
        this.connect = 0;
        this.connectPress = false;
        this.connectLast = false;
        this.dup = 0;
        this.dupPress = false;
        this.dupLast = false;
        this.ddown = 0;
        this.ddownPress = false;
        this.ddownLast = false;
        this.dleft = 0;
        this.dleftPress = false;
        this.dleftLast = false;
        this.dright = 0;
        this.drightPress = false;
        this.drightLast = false;
        this.rleft = 0;
        this.rright = 0;
        this.rup = 0;
        this.rdown = 0;
        this.hmoveLast = 0;
        this.hmove = 0;
        this.vmoveLast = 0;
        this.vmove = 0;
        this.hright = 0;
        this.vright = 0;
        this.reversible = 0;
        this.left = 0;
        this.leftTap = 120;
        this.leftUntap = 120;
        this.right = 0;
        this.rightTap = 120;
        this.rightUntap = 120;
        this.up = 0;
        this.upTap = 120;
        this.upUntap = 120;
        this.down = 0;
        this.downTap = 120;
        this.downUntap = 120;
    }
    applyBindings(layoutMap) {
        const layout = this.layout;
        if (layout.name === 'keyboard') {
            const gamepad = this.gamepad;
            for (const key in layoutMap) {
                if (!utils_1.objHas(layoutMap, key)) {
                    continue;
                }
                let action = layoutMap[key];
                let value = 0;
                switch (action) {
                    case 'up':
                        action = 'vmove';
                        value = -1;
                        break;
                    case 'down':
                        action = 'vmove';
                        value = 1;
                        break;
                    case 'left':
                        action = 'hmove';
                        value = -1;
                        break;
                    case 'right':
                        action = 'hmove';
                        value = 1;
                        break;
                    case 'utilt':
                        action = 'vmove';
                        value = -0.5;
                        break;
                    case 'dtilt':
                        action = 'vmove';
                        value = 0.7;
                        break;
                    case 'ltilt':
                        action = 'hmove';
                        value = -0.7;
                        break;
                    case 'rtilt':
                        action = 'hmove';
                        value = 0.7;
                        break;
                    case 'rup':
                        action = 'vright';
                        value = -1;
                        break;
                    case 'rdown':
                        action = 'vright';
                        value = 1;
                        break;
                    case 'rleft':
                        action = 'hright';
                        value = -1;
                        break;
                    case 'rright':
                        action = 'hright';
                        value = 1;
                        break;
                    case 'shield':
                        action = 'shield1';
                        value = 1;
                        break;
                    case 'shield1':
                        value = 1;
                        break;
                    case 'shield2':
                        value = 1;
                        break;
                }
                if (action.includes(' ')) {
                    const split = action.split(' ');
                    action = split[0];
                    value = parseFloat(split[1]);
                }
                if (gamepad.buttonBinds.has(key)) {
                    gamepad.buttonBinds.delete(key);
                }
                if (gamepad.axisBinds.has(key)) {
                    gamepad.axisBinds.delete(key);
                }
                if (gamepad.modifiers.has(key)) {
                    gamepad.modifiers.delete(key);
                }
                if (exports.axisList.includes(action)) {
                    gamepad.axisBinds.set(key, {
                        axis: this.axisMap.indexOf(action),
                        value: math_1.bround(value),
                    });
                    continue;
                }
                if (exports.buttonList.includes(action)) {
                    gamepad.buttonBinds.set(key, this.buttonMap.indexOf(action));
                    continue;
                }
            }
            gamepad.updateBuffers();
            return true;
        }
        if (!layout.buttonNames || !layout.axisNames) {
            console.log('Cannot apply layout to', this.name);
            return false;
        }
        for (const input in layoutMap) {
            if (!utils_1.objHas(layoutMap, input)) {
                continue;
            }
            let action = layoutMap[input];
            if (action.includes(' ')) {
                action = action.split(' ')[0];
                continue;
            }
            const buttonIndex = layout.buttonNames.indexOf(input);
            if (buttonIndex !== -1) {
                if (!exports.buttonList.includes(action)) {
                    console.log('Unknown button:', action);
                    continue;
                }
                this.buttonMap[buttonIndex] = action;
                continue;
            }
            const axisIndex = layout.axisNames.indexOf(input);
            if (axisIndex !== -1) {
                if (!exports.axisList.includes(action)) {
                    console.log('Unknown axis:', action);
                    continue;
                }
                this.axisMap[buttonIndex] = action;
            }
        }
        return true;
    }
    reloadMapping() {
        const layoutFile = terminal_1.getFile(`/home/mapping/${this.layout.name}.${this.mapping}`);
        if (layoutFile !== null) {
            const mapping = JSONC.parse(layoutFile.data);
            if (!mapping || typeof mapping !== 'object') {
                terminal_1.dbg('Invalid layout file: /home/mapping/', `${this.layout.name}.${this.mapping}`);
            }
            this.applyBindings(mapping);
        }
        else {
            this.resetMapping();
        }
    }
    resetMapping() {
        if (this.layout.name === 'keyboard') {
            ;
            this.gamepad.loadBindings(exports.defaultKeyboard);
            return;
        }
        this.buttonMap = [...this.layout.buttons];
        this.axisMap = [...this.layout.axes];
    }
    getMapping() {
        const layout = {};
        if (this.layout.name === 'keyboard') {
            const gamepad = this.gamepad;
            for (const [k, v] of gamepad.buttonBinds) {
                const name = this.buttonMap[v];
                if (exports.defaultKeyboard.buttonBinds[k] !== name) {
                    layout[k] = name;
                }
            }
            for (const [k, v] of gamepad.axisBinds) {
                const name = this.axisMap[v.axis];
                if (!utils_1.objHas(exports.defaultKeyboard.axisBinds, k)
                    || exports.defaultKeyboard.axisBinds[k].axis !== name
                    || exports.defaultKeyboard.axisBinds[k].value !== v.value) {
                    layout[k] = name + ' ' + v.value;
                }
            }
            return layout;
        }
        for (let i = 0; i < this.buttonMap.length; i++) {
            if (this.buttonMap[i] !== this.layout.buttons[i]) {
                layout[this.layout.buttonNames[i]] = this.buttonMap[i];
            }
        }
        for (let i = 0; i < this.axisMap.length; i++) {
            if (this.axisMap[i] !== this.layout.axes[i]) {
                layout[this.layout.axisNames[i]] = this.axisMap[i];
            }
        }
        return layout;
    }
    getFrame() {
        const frame = new InputFrame();
        const gamepad = this.gamepad;
        const buttonMap = this.buttonMap;
        const axisMap = this.axisMap;
        frame.controllerID = this.controllerID;
        for (let i = 0; i < buttonMap.length; i++) {
            const name = buttonMap[i];
            frame[name] = Math.max(gamepad.buttons[i], frame[name]);
        }
        for (let i = 0; i < axisMap.length; i++) {
            const shieldMin = gamepad.shieldDeadzone;
            const name = axisMap[i];
            let v = gamepad.axes[i];
            if (this.calibrate) {
                const calibration = this.calibration;
                if (name === 'shield1' || name === 'shield2') {
                    v = Math.min(1, Math.max(0, (v - calibration[name + 'min'])
                        / (calibration[name + 'max'] - calibration[name + 'min'])));
                    if (v < shieldMin) {
                        v = 0;
                    }
                    else {
                        v = (v - shieldMin) / (1.0 - shieldMin);
                    }
                }
                else if (v < 0) {
                    v = Math.max(-1, v / -calibration[name + 'min']);
                }
                else {
                    v = Math.min(v / calibration[name + 'max'], 1);
                }
            }
            frame[name] = v;
        }
        return frame;
    }
    poll() {
        const frame = this.getFrame();
        if (frame.hmove * frame.hmove + frame.vmove * frame.vmove < deadZone) {
            frame.hmove = 0;
            frame.vmove = 0;
        }
        else {
            let angle = math_1.computeAngle(-frame.hmove, frame.vmove);
            if (angle > 360 - snapDeg || angle < snapDeg) {
                angle = 0;
                frame.hmove = Math.sqrt(frame.hmove * frame.hmove + frame.vmove * frame.vmove);
                frame.vmove = 0;
            }
            else if (angle > 180 - snapDeg && angle < 180 + snapDeg) {
                angle = 180;
                frame.hmove = -Math.sqrt(frame.hmove * frame.hmove + frame.vmove * frame.vmove);
                frame.vmove = 0;
            }
            else if (angle > 90 - snapDeg && angle < 90 + snapDeg) {
                angle = 90;
                frame.vmove = -Math.sqrt(frame.hmove * frame.hmove + frame.vmove * frame.vmove);
                frame.hmove = 0;
            }
            else if (angle > 270 - snapDeg && angle < 270 + snapDeg) {
                angle = 270;
                frame.vmove = Math.sqrt(frame.hmove * frame.hmove + frame.vmove * frame.vmove);
                frame.hmove = 0;
            }
            if (frame.hmove * frame.hmove + frame.vmove * frame.vmove > 1) {
                frame.hmove = math_1.angleX(angle);
                frame.vmove = -math_1.angleY(angle);
            }
        }
        if (!this.digitalShield) {
            if (frame.shield1 > this.digitalShieldThreshold) {
                frame.shield1 = 1;
                frame.shield1Hard = 1;
            }
            if (frame.shield2 > this.digitalShieldThreshold) {
                frame.shield2 = 1;
                frame.shield2Hard = 1;
            }
        }
        frame.hmove = math_1.bround(frame.hmove);
        frame.vmove = math_1.bround(frame.vmove);
        frame.hright = math_1.bround(frame.hright);
        frame.vright = math_1.bround(frame.vright);
        frame.shield1 = math_1.bround(frame.shield1);
        frame.shield2 = math_1.bround(frame.shield2);
        return frame;
    }
    step(frame) {
        this.attackPress = false;
        this.jumpPress = false;
        this.specialPress = false;
        this.grabPress = false;
        this.shield1Press = false;
        this.shield2Press = false;
        this.shield1HardPress = false;
        this.shield2HardPress = false;
        this.selectPress = false;
        this.startPress = false;
        this.dupPress = false;
        this.ddownPress = false;
        this.dleftPress = false;
        this.drightPress = false;
        for (let i = 0; i < exports.buttonList.length; i++) {
            const name = exports.buttonList[i];
            if (!this[last[name]] && frame[name] > 0) {
                ;
                this[press[name]] = true;
                this[last[name]] = true;
            }
        }
        this.attack = frame.attack;
        this.jump = frame.jump;
        this.special = frame.special;
        this.grab = frame.grab;
        this.shield1 = frame.shield1;
        this.shield2 = frame.shield2;
        this.shield1Hard = frame.shield1Hard;
        this.shield2Hard = frame.shield2Hard;
        this.select = frame.select;
        this.start = frame.start;
        this.dup = frame.dup;
        this.ddown = frame.ddown;
        this.dleft = frame.dleft;
        this.dright = frame.dright;
        this.hmoveLast = this.hmove;
        this.hmove = frame.hmove;
        this.vmoveLast = this.vmove;
        this.vmove = frame.vmove;
        this.hright = frame.hright;
        this.vright = frame.vright;
        if (this.shield1Hard) {
            if (!this.shield1Press && this.shield1HardPress) {
                this.shield1Press = true;
                this.shield1Last = true;
            }
            if (!this.shield2Press && this.shield2HardPress) {
                this.shield2Press = true;
                this.shield2Last = true;
            }
        }
        if (this.shield1Hard) {
            this.shield1 = 1;
        }
        if (this.shield2Hard) {
            this.shield2 = 1;
        }
        if (this.shield1 > 0 && !this.shield1Last) {
            this.shield1Press = true;
            this.shield1Last = true;
        }
        if (this.shield2 > 0 && !this.shield2Last) {
            this.shield2Press = true;
            this.shield2Last = true;
        }
        for (let i = 0; i < exports.buttonList.length; i++) {
            ;
            this[last[exports.buttonList[i]]] = this[exports.buttonList[i]];
        }
        this.shield1Last = this.shield1 > 0;
        this.shield2Last = this.shield2 > 0;
        this.shield = Math.max(0, this.shield1, this.shield2);
        this.shieldPress = this.shield1Press || this.shield2Press;
        this.shieldHard = Math.max(this.shield1Hard, this.shield2Hard);
        this.shieldHardPress = this.shield1HardPress || this.shield2HardPress;
        {
            const tapDistance = 0.7 ** 2;
            const htapDistance = 0.7;
            const utapDistance = 0.7;
            const dtapDistance = 0.8;
            const untapDistance = 0.55;
            const rightDeadzone = 0.4;
            const tapTime = 3;
            const tapReset = 120;
            const up = this.vmove < 0 && Math.abs(this.vmove) > Math.abs(this.hmove) - 0.1;
            const down = this.vmove > 0 && Math.abs(this.vmove) > Math.abs(this.hmove) - 0.1;
            const left = this.hmove < 0 && Math.abs(this.vmove) < Math.abs(this.hmove) - 0.1;
            const right = this.hmove > 0 && Math.abs(this.vmove) < Math.abs(this.hmove) - 0.1;
            const rup = this.vright < -rightDeadzone
                && Math.abs(this.vright) > Math.abs(this.hright);
            const rdown = this.vright > rightDeadzone
                && Math.abs(this.vright) > Math.abs(this.hright);
            const rleft = this.hright < -rightDeadzone
                && Math.abs(this.vright) < Math.abs(this.hright);
            const rright = this.hright > rightDeadzone
                && Math.abs(this.vright) < Math.abs(this.hright);
            const squareDistance = this.hmove * this.hmove + this.vmove * this.vmove;
            this.up = this.up && up ? 1 : this.up === 0 && up ? 2 : 0;
            this.down = this.down && down ? 1 : this.down === 0 && down ? 2 : 0;
            this.left = this.left && left ? 1 : this.left === 0 && left ? 2 : 0;
            this.right = this.right && right ? 1 : this.right === 0 && right ? 2 : 0;
            this.rup = this.rup && rup ? 1 : this.rup === 0 && rup ? 2 : 0;
            this.rdown = this.rdown && rdown ? 1 : this.rdown === 0 && rdown ? 2 : 0;
            this.rleft = this.rleft && rleft ? 1 : this.rleft === 0 && rleft ? 2 : 0;
            this.rright
                = this.rright && rright ? 1 : this.rright === 0 && rright ? 2 : 0;
            this.upTap = this.upTap + 1;
            this.upUntap = this.upUntap + 1;
            if (this.vmove > -utapDistance) {
                this.upTap = tapReset;
            }
            if (this.vmove > -untapDistance) {
                this.upUntap = 0;
            }
            if (squareDistance > tapDistance
                && this.upUntap < tapTime
                && this.vmove < -utapDistance) {
                this.upTap = 0;
                this.upUntap = tapReset;
            }
            this.downTap = this.downTap + 1;
            this.downUntap = this.downUntap + 1;
            if (this.vmove < dtapDistance) {
                this.downTap = tapReset;
            }
            if (this.vmove < untapDistance) {
                this.downUntap = 0;
            }
            if (squareDistance > tapDistance
                && this.downUntap < tapTime
                && this.vmove > dtapDistance) {
                this.downTap = 0;
                this.downUntap = tapReset;
            }
            this.leftUntap = this.leftUntap + 1;
            this.leftTap = this.leftTap + 1;
            if (this.hmove > -htapDistance) {
                this.leftTap = tapReset;
            }
            if (this.hmove > -untapDistance) {
                this.leftUntap = 0;
            }
            if (squareDistance > tapDistance
                && this.leftUntap < tapTime
                && this.hmove < -htapDistance) {
                this.leftTap = 0;
                this.leftUntap = tapReset;
            }
            this.rightUntap = this.rightUntap + 1;
            this.rightTap = this.rightTap + 1;
            if (this.hmove < htapDistance) {
                this.rightTap = tapReset;
            }
            if (this.hmove < untapDistance) {
                this.rightUntap = 0;
            }
            if (squareDistance > tapDistance
                && this.rightUntap < tapTime
                && this.hmove > htapDistance) {
                this.rightTap = 0;
                this.rightUntap = tapReset;
            }
        }
        this.frame = frame;
    }
    angle() {
        return math_1.computeAngle(this.hmove, this.vmove);
    }
    distance() {
        return Math.sqrt(this.hmove * this.hmove + this.vmove * this.vmove);
    }
    radians() {
        return math_1.computeRadians(this.hmove, this.vmove);
    }
    rangle() {
        return math_1.computeAngle(this.hright, this.vright);
    }
    angleX() {
        return math_1.angleX(math_1.computeAngle(this.hmove, this.vmove));
    }
    angleY() {
        return math_1.angleY(math_1.computeAngle(this.hmove, this.vmove));
    }
}
exports.Controller = Controller;
exports.keyboardGamepads = [];
let keyboardControllers = [];
class KeyboardGamepad {
    constructor(binds) {
        this.id = 'keyboard';
        this.kind = 'keyboard';
        this.shieldDeadzone = 0;
        this.controller = null;
        this.pressed = false;
        this.loadBindings(binds);
        this.buttons = new Uint8Array(exports.layouts.keyboard.buttons.length);
        this.pressCount = new Uint32Array(exports.layouts.keyboard.buttons.length);
        this.axes = new Float32Array(exports.layouts.keyboard.axes.length);
        this.rawAxes = new Float32Array(exports.layouts.keyboard.axes.length);
    }
    loadBindings(binds) {
        const { buttonBinds, axisBinds, modifiers } = binds;
        this.buttonBinds = new Map();
        for (const k of Object.getOwnPropertyNames(buttonBinds)) {
            this.buttonBinds.set(k, exports.layouts.keyboard.buttons.indexOf(buttonBinds[k]));
        }
        this.axisBinds = new Map();
        for (const k of Object.getOwnPropertyNames(axisBinds)) {
            const v = axisBinds[k];
            const axis = exports.layouts.keyboard.axes.indexOf(v.axis);
            this.axisBinds.set(k, {
                axis: axis,
                value: math_1.bround(v.value),
            });
        }
        this.modifiers = new Map();
        for (const k of Object.getOwnPropertyNames(modifiers)) {
            this.modifiers.set(k, axisModifier[modifiers[k]]);
        }
        this.updateBuffers();
    }
    updateBuffers() {
        this.axisQueue = new Map();
        for (const [_, v] of this.axisBinds) {
            const axis = v.axis;
            let len = 0;
            if (this.axisQueue.has(axis)) {
                len = this.axisQueue.get(axis).array.length + 1;
            }
            this.axisQueue.set(axis, { length: 0, array: new Float32Array(len) });
        }
        for (const [key, arr] of this.axisQueue) {
            if (arr.array.length === 0) {
                this.axisQueue.delete(key);
            }
        }
        this.activeModifiers = new Uint8Array(this.modifiers.size);
    }
    keydown(key) {
        if (this.buttonBinds.has(key)) {
            const index = this.buttonBinds.get(key);
            this.buttons[index] = 1;
            this.pressCount[index]++;
            return true;
        }
        if (this.axisBinds.has(key)) {
            const { axis, value } = this.axisBinds.get(key);
            if (this.rawAxes[axis] !== 0 && this.axisQueue.has(axis)) {
                const a = this.axisQueue.get(axis);
                a.array[a.length] = this.axes[axis];
                a.length++;
            }
            this.axes[axis] = value;
            this.rawAxes[axis] = value;
            return true;
        }
        return false;
    }
    keyup(key) {
        if (this.buttonBinds.has(key)) {
            const index = this.buttonBinds.get(key);
            this.pressCount[index]--;
            if (this.pressCount[index] <= 0) {
                this.buttons[index] = 0;
            }
            return true;
        }
        if (this.axisBinds.has(key)) {
            const { axis, value } = this.axisBinds.get(key);
            if (!this.axisQueue.has(axis)) {
                this.axes[axis] = 0;
                this.rawAxes[axis] = 0;
                return true;
            }
            {
                const aq = this.axisQueue.get(axis);
                if (aq.length === 0) {
                    this.axes[axis] = 0;
                    this.rawAxes[axis] = 0;
                    return true;
                }
                if (value === this.axes[axis]) {
                    aq.length--;
                    this.axes[axis] = aq.array[aq.length];
                    this.rawAxes[axis] = aq.array[aq.length];
                    return true;
                }
                for (let i = aq.length - 1; i >= 0; i--) {
                    if (aq.array[i] === value) {
                        aq.length--;
                        for (; i < aq.length; i++) {
                            aq.array[i] = aq.array[i + 1];
                        }
                        return true;
                    }
                }
                console.log('middle queue; not found');
            }
            return true;
        }
        return false;
    }
}
class GamepadState {
    constructor(nButtons, nAxes, mapping, id, which) {
        this.connected = false;
        this.controller = null;
        this.pressed = false;
        this.buttons = new Uint8Array(nButtons);
        this.axes = new Float32Array(nAxes);
        this.kind = mapping;
        this.id = id;
        this.which = which;
    }
}
const gamepadData = new Map();
const SDL_CONTROLLER_BUTTON_MAX = 15;
const SDL_CONTROLLER_AXIS_MAX = 6;
const keyPresses = new Set();
const ignorePressed = new Set([
    'LSHIFT',
    'RSHIFT',
    'LCTRL',
    'RCTRL',
    'LALT',
    'RALT',
    'LGUI',
    'RGUI',
    'RETURN',
    'ESCAPE',
    'BACKSPACE',
    'DELETE',
    'TAB',
    'SPACE',
    'CAPSLOCK',
    'NUMLOCK',
    'SCROLLLOCK',
    'PRINTSCREEN',
    'PAUSE',
]);
const pollEvent = (e) => {
    let state = null;
    switch (e.type) {
        case 8:
            state = new GamepadState(SDL_CONTROLLER_BUTTON_MAX, SDL_CONTROLLER_AXIS_MAX, 'standard', 'XInput Controller', e.which);
            gamepadData.set(e.which, state);
            exports.unconnected.push(new Controller(state));
            state.controller = exports.unconnected[exports.unconnected.length - 1];
            return true;
        case 9:
            exports.disconnecting.push(gamepadData.get(e.which).controller);
            return true;
        case 11:
            state = gamepadData.get(e.which);
            state.axes[e.axis] = e.value;
            if (e.value > 0.8 || e.value < -0.8) {
                state.pressed = true;
            }
            return true;
        case 13:
        case 12:
            state = gamepadData.get(e.which);
            state.buttons[e.button] = e.state;
            if (e.state !== 0) {
                state.pressed = true;
            }
            return true;
        case 6:
            if (!e.repeat && !keyPresses.has(e.key)) {
                keyPresses.add(e.key);
                for (let i = 0; i < exports.keyboardGamepads.length; i++) {
                    const g = exports.keyboardGamepads[i];
                    if (g.keydown(e.key)) {
                        if (!g.pressed && !ignorePressed.has(e.key)) {
                            g.pressed = true;
                            return true;
                        }
                        else if (g.pressed) {
                            return true;
                        }
                    }
                }
            }
            break;
        case 5:
            if (keyPresses.has(e.key)) {
                keyPresses.delete(e.key);
                for (let i = 0; i < exports.keyboardGamepads.length; i++) {
                    exports.keyboardGamepads[i].keyup(e.key);
                    return true;
                }
            }
            break;
    }
    return false;
};
exports.pollEvent = pollEvent;
const addController = (controller) => {
    console.log(`Controller connected: ${controller.name} ${controller.gamepad ? controller.gamepad.id : ''}`);
    if (controller.seed === null) {
        controller.seed = math_1.generateSeed();
    }
    exports.connected.push(controller);
    if (controller.portNumber !== -1) {
        let found = false;
        for (let i = 0; i < exports.connected.length - 1; i++) {
            if (exports.connected[i].portNumber === controller.portNumber) {
                found = true;
                break;
            }
        }
        if (!found) {
            return;
        }
    }
    for (let portNumber = 0; portNumber < exports.connected.length; portNumber++) {
        let found = false;
        for (let j = 0; j < exports.connected.length - 1; j++) {
            if (exports.connected[j].portNumber === portNumber) {
                found = true;
                break;
            }
        }
        if (!found) {
            controller.portNumber = portNumber;
            break;
        }
    }
};
exports.addController = addController;
const checkConnections = () => {
    if (gcConnecting.length > 0) {
        for (let i = 0; i < gcConnecting.length; i++) {
            const controller = new Controller(gcConnecting[i]);
            gcConnecting[i].controller = controller;
            exports.connecting.push(controller);
        }
        gcConnecting.length = 0;
    }
    for (let i = 0; i < exports.unconnected.length; i++) {
        const controller = exports.unconnected[i];
        if (controller.gamepad.pressed) {
            exports.connecting.push(controller);
            exports.unconnected.splice(i, 1);
            i--;
            if (controller.gamepad instanceof KeyboardGamepad) {
                keyboardConnected = true;
                for (let j = 0; j < exports.unconnected.length; j++) {
                    if (exports.unconnected[j].gamepad instanceof KeyboardGamepad) {
                        exports.unconnected.splice(j, 1);
                        j--;
                    }
                }
                break;
            }
        }
    }
};
exports.checkConnections = checkConnections;
const processConnection = (frame, destroy) => {
    if (exports.disconnecting.length > 0) {
        for (let i = 0; i < exports.disconnecting.length; i++) {
            const controller = exports.disconnecting[i];
            const index = exports.connected.indexOf(controller);
            const uindex = exports.unconnected.indexOf(controller);
            if (index !== -1) {
                exports.connected.splice(index, 1);
                if (controller.client) {
                    exports.inputCapacitor.disconnect(controller.client);
                }
                destroy(controller);
                if (controller.name !== 'network') {
                    networking_1.netDisconnect(controller);
                }
            }
            if (controller.name === 'network') {
                continue;
            }
            if (uindex !== -1) {
                exports.unconnected.splice(uindex, 1);
            }
        }
        exports.disconnecting.length = 0;
    }
    if (exports.connecting.length === 0) {
        return;
    }
    for (let i = 0; i < exports.connecting.length; i++) {
        const controller = exports.connecting[i];
        exports.waiting.push(controller);
        console.log(`Registered controller ${controller.name} on frame ${frame}`);
        if (!controller.client) {
            const client = exports.inputCapacitor.connect({ sizeOffset: frame });
            controller.client = client;
            console.log('Connected new controller client');
        }
    }
    exports.connecting.length = 0;
};
exports.processConnection = processConnection;
const tickConnections = (frame, create) => {
    let found = false;
    for (let i = 0; i < exports.waiting.length; i++) {
        const controller = exports.waiting[i];
        if (controller.client.sizeOffset === frame) {
            found = true;
            exports.waiting.splice(i--, 1);
            exports.addController(controller);
            create(controller);
            console.log('Created controller:', controller.name, controller.client.sizeOffset, frame, controller.client.size);
            if (controller.name !== 'network') {
                networking_1.netConnect(controller);
            }
        }
        else {
            console.log('not yet!', controller.client.sizeOffset, frame, controller.client.size);
        }
    }
    if (found) {
        exports.connected.sort((a, b) => a.portNumber - b.portNumber);
        engine_1.players.sort((a, b) => a.sourceController?.portNumber - b.sourceController?.portNumber);
        engine_1.entities.sort((a, b) => a.sourceController?.portNumber - b.sourceController?.portNumber);
    }
};
exports.tickConnections = tickConnections;
const collapsePorts = () => {
    for (let i = 0; i < exports.connected.length; i++) {
        exports.connected[i].portNumber = i;
    }
};
exports.collapsePorts = collapsePorts;
var axisModifier;
(function (axisModifier) {
    axisModifier[axisModifier["none"] = 0] = "none";
    axisModifier[axisModifier["halfMove"] = 1] = "halfMove";
})(axisModifier = exports.axisModifier || (exports.axisModifier = {}));
exports.defaultKeyboard = {
    buttonBinds: {
        j: 'attack',
        h: 'grab',
        e: 'grab',
        LSHIFT: 'shield1Hard',
        RSHIFT: 'shield2Hard',
        m: 'special',
        A_1: 'dup',
        A_2: 'dleft',
        A_3: 'ddown',
        A_4: 'dright',
        BACKSPACE: 'start',
        i: 'jump',
        p: 'jump',
        SPACE: 'jump',
    },
    axisBinds: {
        w: { axis: 'vmove', value: -1 },
        a: { axis: 'hmove', value: -1 },
        s: { axis: 'vmove', value: 1 },
        d: { axis: 'hmove', value: 1 },
        q: { axis: 'shield1', value: 0.2 },
        QUOTE: { axis: 'shield2', value: 0.2 },
        r: { axis: 'vmove', value: -0.5 },
        f: { axis: 'hmove', value: -0.7 },
        c: { axis: 'vmove', value: 0.7 },
        g: { axis: 'hmove', value: 0.7 },
        o: { axis: 'vright', value: -1 },
        k: { axis: 'hright', value: -1 },
        l: { axis: 'vright', value: 1 },
        SEMICOLON: { axis: 'hright', value: 1 },
    },
    modifiers: {},
};
const readyKeyboardControllers = () => {
    exports.keyboardGamepads.push(new KeyboardGamepad(exports.defaultKeyboard));
    keyboardControllers = exports.keyboardGamepads.map(item => new Controller(item));
    exports.unconnected.push(...keyboardControllers);
};
exports.readyKeyboardControllers = readyKeyboardControllers;
const getAllMappings = () => {
    const d = terminal_1.getDir('/home/mapping');
    const mappings = [];
    for (const k in exports.layouts) {
        if (!utils_1.objHas(exports.layouts, k)) {
            continue;
        }
        if (d === null || !d.contents.has(k + '.default')) {
            const mapping = {};
            if (k === 'keyboard') {
                for (const b of Object.getOwnPropertyNames(exports.defaultKeyboard.buttonBinds)) {
                    mapping[b]
                        = exports.defaultKeyboard.buttonBinds[b];
                }
                for (const b of Object.getOwnPropertyNames(exports.defaultKeyboard.axisBinds)) {
                    const action = exports.defaultKeyboard.axisBinds[b];
                    mapping[b]
                        = (action.value < 0 ? '-' : '')
                            + (Math.abs(action.value) < 0.95 ? '0.' : '')
                            + action.axis;
                }
            }
            else {
                const l = exports.layouts[k];
                for (let i = 0; i < l.buttonNames.length; i++) {
                    mapping[l.buttonNames[i]] = l.buttons[i];
                }
                for (let i = 0; i < l.axisNames.length; i++) {
                    mapping[l.axisNames[i]] = l.axes[i];
                }
            }
            mappings.push({ layout: exports.layouts[k], name: 'default', changes: {}, mapping });
        }
    }
    if (d === null) {
        return mappings;
    }
    for (const [fn, f] of d.contents) {
        if (!terminal_1.isFile(f)
            || !fn.includes('.')
            || !utils_1.objHas(exports.layouts, fn.split('.')[0])) {
            continue;
        }
        const data = JSONC.parse(f.data);
        const k = fn.split('.')[0];
        const name = fn.split('.')[1];
        const mapping = {};
        if (k === 'keyboard') {
            for (const b of Object.getOwnPropertyNames(exports.defaultKeyboard.buttonBinds)) {
                mapping[b]
                    = exports.defaultKeyboard.buttonBinds[b];
                if (utils_1.objHas(data, b)) {
                    mapping[b] = data[b];
                }
                if (mapping[b] === '') {
                    delete mapping[b];
                }
            }
            for (const b of Object.getOwnPropertyNames(exports.defaultKeyboard.axisBinds)) {
                const action = exports.defaultKeyboard.axisBinds[b];
                mapping[b]
                    = (action.value < 0 ? '-' : '')
                        + (Math.abs(action.value) < 0.95 ? '0.' : '')
                        + action.axis;
                if (utils_1.objHas(data, b)) {
                    const v = data[b].split(' ')[1];
                    mapping[b]
                        = (v < 0 ? '-' : '')
                            + (Math.abs(v) < 0.95 ? '0.' : '')
                            + data[b].split(' ')[0];
                }
                if (mapping[b] === '') {
                    delete mapping[b];
                }
            }
        }
        else {
            const l = exports.layouts[k];
            for (let i = 0; i < l.buttonNames.length; i++) {
                const b = l.buttonNames[i];
                mapping[b] = l.buttons[i];
                if (utils_1.objHas(data, b)) {
                    mapping[b] = data[b];
                }
                if (mapping[b] === '') {
                    delete mapping[b];
                }
            }
            for (let i = 0; i < l.axisNames.length; i++) {
                const b = l.axisNames[i];
                mapping[b] = l.axes[i];
                if (utils_1.objHas(data, b)) {
                    mapping[b] = data[b];
                }
                if (mapping[b] === '') {
                    delete mapping[b];
                }
            }
        }
        mappings.push({
            layout: exports.layouts[k],
            name: name,
            changes: data,
            mapping: mapping,
        });
    }
    return mappings;
};
exports.getAllMappings = getAllMappings;
const saveMapping = (mapping, values) => {
    let d = terminal_1.getDir('/home/mapping');
    if (d === null) {
        terminal_1.addDirToIndex('/home', {
            contents: utils_1.map({
                mapping: {
                    contents: utils_1.map({}),
                },
            }),
        });
        d = terminal_1.getDir('/home/mapping');
    }
    const filename = `/home/mapping/${mapping.layout.name}.${mapping.name}`;
    return terminal_1.getShell()
        .execute('touch ' + filename)
        .then(() => {
        const file = terminal_1.getFile(filename);
        file.data = JSON.stringify(values) + '\n';
        for (const c of exports.connected) {
            if (c.mapping === mapping.name
                && c.layout !== null
                && c.layout.name === mapping.layout.name) {
                c.reloadMapping();
            }
        }
    });
};
exports.saveMapping = saveMapping;
const resetMapping = (mapping) => {
    const filename = `/home/mapping/${mapping.layout.name}.${mapping.name}`;
    return terminal_1.getShell()
        .execute('rm ' + filename)
        .then(() => {
        for (const c of exports.connected) {
            if (c.mapping === mapping.name
                && c.layout !== null
                && c.layout.name === mapping.layout.name) {
                c.reloadMapping();
            }
        }
    });
};
exports.resetMapping = resetMapping;
const binObj = {
    controller: {
        mode: 0b001,
        exec: (args, _stdin, stdout, _stderr, _wd, _env) => {
            let controllers = exports.connected;
            if (args.includes('all')) {
                controllers = exports.connected
                    .concat(exports.unconnected)
                    .sort((a, b) => a.controllerID - b.controllerID);
            }
            for (let i = 0; i < controllers.length; i++) {
                const c = controllers[i];
                if (c.gamepad) {
                    stdout.write(`${c.controllerID}. ${c.gamepad.id}: ${c.gamepad.kind}\n`);
                }
            }
            return 0;
        },
    },
    capacitor: {
        mode: 0b001,
        exec: (_args, _stdin, stdout, _stderr, _wd, _env) => {
            stdout.write('Resetting capacitor\n');
            exports.resetConnected();
            for (const c of exports.connected) {
                stdout.write(`  Reset ${c.controllerID}. ${c.gamepad.id}: ${c.gamepad.kind}\n`);
            }
            return 0;
        },
    },
    mapping: {
        mode: 0b001,
        exec: (args, _stdin, stdout, stderr, _wd, _env) => {
            if (args.length < 2) {
                stdout.write(`Usage: mapping [controller ID]
  Prints mapping for controller ID, as well as lists available button/axis actions
Usage: mapping [controller ID] bind [button/axis name] [button/axis action] [axis value (keyboard only)]
  Binds button/axis to action for controller ID
Usage: mapping [controller ID] save [rename mapping]
  Saves current mapping as the default for controller's kind
Usage: mapping [controller ID] reset
  Resets mapping to original values for controller's kind
Usage: mapping [controller ID] default
  Resets mapping to current default for controller's kind
Usage: mapping [controller ID] rename [mapping name]
  Changes the controller's mapping name

Usage: mapping list
  Lists all default and saved mappings
Usage: mapping list [controller kind]
  Lists all default and saved mappings for controller kind (e.g. standard, gcn_native)

See also: controller (all)
  Lists active controllers; if all is specified, includes all connected controllers
`);
                return 0;
            }
            let d = terminal_1.getDir('/home/mapping');
            if (d === null) {
                terminal_1.addDirToIndex('/home', {
                    contents: utils_1.map({
                        mapping: {
                            contents: utils_1.map({}),
                        },
                    }),
                });
                d = terminal_1.getDir('/home/mapping');
            }
            const printLayout = (layout, newline) => {
                if (layout.name !== 'keyboard') {
                    const names = !layout.buttonNames
                        ? 'N/A'
                        : layout.buttonNames
                            .map((b, i) => {
                            return `${b} ${layout.buttons[i] ? layout.buttons[i] : 'unbound'}`;
                        })
                            .join(newline ? '\n  ' : ', ');
                    stdout.write(`${layout.name}:\n  ${names}\n`);
                    return;
                }
                const binds = [];
                for (const k in exports.defaultKeyboard.buttonBinds) {
                    if (!utils_1.objHas(exports.defaultKeyboard.buttonBinds, k)) {
                        continue;
                    }
                    binds.push(`${k} ${exports.defaultKeyboard.buttonBinds[k]}`);
                }
                binds.push(newline ? '' : '\n');
                for (const k in exports.defaultKeyboard.axisBinds) {
                    if (!utils_1.objHas(exports.defaultKeyboard.axisBinds, k)) {
                        continue;
                    }
                    binds.push(`${k} ${exports.defaultKeyboard.axisBinds[k].axis} ${exports.defaultKeyboard.axisBinds[k].value}`);
                }
                if (binds.length > 0) {
                    binds[0] = '  ' + binds[0];
                }
                stdout.write(`${layout.name}:\n${binds
                    .join(newline ? '\n  ' : ', ')
                    .replace(/\n, /g, '\n  ')}\n`);
            };
            if (isNaN(parseFloat(args[1]))) {
                let filter = '';
                if (args.length > 2) {
                    filter = args[2];
                }
                switch (args[1]) {
                    case 'list':
                        for (const k in exports.layouts) {
                            if (!utils_1.objHas(exports.layouts, k)) {
                                continue;
                            }
                            if (filter !== '' && filter !== k) {
                                continue;
                            }
                            printLayout(exports.layouts[k], filter !== '');
                        }
                        for (const [name, f] of d.contents) {
                            if (!terminal_1.isFile(f)) {
                                continue;
                            }
                            if (filter !== '' && filter !== name.split('.')[0]) {
                                continue;
                            }
                            stdout.write(`${name}: ${f.data}\n`);
                        }
                        return 0;
                    default:
                        stderr.write(`Unknown command: ${args[1]}\n`);
                        return 1;
                }
            }
            const id = parseInt(args[1].toLowerCase(), 10);
            let controller = null;
            const controllers = exports.connected.concat(exports.unconnected);
            for (let i = 0; i < controllers.length; i++) {
                const c = controllers[i];
                if (c.controllerID === id) {
                    controller = c;
                    break;
                }
            }
            if (!controller) {
                stderr.write(`Unable to find controller with ID ${id}\n`);
                return 1;
            }
            if (controller.layout === null) {
                stdout.write('Controller has no layout\n');
                return 0;
            }
            const layout = controller.layout;
            if (!layout.buttonNames && layout.name !== 'keyboard') {
                stdout.write('Controller is not rebindable yet\n');
                return 0;
            }
            if (args.length === 2) {
                if (layout.name !== 'keyboard') {
                    for (let i = 0; i < layout.buttonNames.length; i++) {
                        stdout.write(`  ${layout.buttonNames[i]} ${layout.buttons[i] ? layout.buttons[i] : 'unbound'}\n`);
                    }
                    stdout.write('\n');
                    for (let i = 0; i < layout.axisNames.length; i++) {
                        stdout.write(`  ${layout.axisNames[i]} ${layout.axes[i] ? layout.axes[i] : 'unbound'}\n`);
                    }
                    return 0;
                }
                const gamepad = controller.gamepad;
                for (const [k, b] of gamepad.buttonBinds) {
                    stdout.write(`  ${k} ${layout.buttons[b]}\n`);
                }
                stdout.write('\n');
                for (const [k, a] of gamepad.axisBinds) {
                    stdout.write(`  ${k} ${layout.axes[a.axis]} ${a.value}\n`);
                }
                return 0;
            }
            switch (args[2]) {
                case 'reset':
                    controller.resetMapping();
                    stdout.write('Reset mapping\n');
                    return 0;
                case 'default':
                    controller.reloadMapping();
                    stdout.write('Reset mapping to default\n');
                    return 0;
                case 'rename':
                    if (args.length < 4) {
                        stderr.write('Need binding name\n');
                        return 1;
                    }
                    layout.name = args[3];
                    return 0;
                case 'save': {
                    if (args.length > 3) {
                        layout.name = args[3];
                    }
                    const fileName = `/home/mapping/${layout.name}.${controller.mapping}`;
                    terminal_1.getShell()
                        .execute(`touch ${fileName}`)
                        .then(() => {
                        const file = terminal_1.getFile(fileName);
                        file.data = JSON.stringify(controller.getMapping()) + '\n';
                    });
                    stdout.write('Saved mapping file\n');
                    return 0;
                }
                case 'bind':
                    if (args.length === 5 || args.length === 6) {
                        return controller.applyBindings({
                            [args[3]]: args.length === 5 ? args[4] : `${args[4]} ${args[5]}`,
                        })
                            ? 0
                            : 1;
                    }
                    if (layout.name !== 'keyboard') {
                        stdout.write(`Button names: ${layout.buttonNames.join(', ')}
Axis names: ${layout.axisNames.join(', ')}

`);
                    }
                    stdout.write(`Button actions: ${exports.buttonList.join(', ')}
Axis actions: ${exports.axisList.join(', ')}

Usage: mapping [controller ID] bind [button/axis name] [button/axis action]${layout.name === 'keyboard' ? ' [axis value (keyboard only)]' : ''}
`);
                    return 0;
            }
            return 0;
        },
    },
};
terminal_1.addDirToIndex('/', {
    contents: utils_1.map({
        bin: {
            contents: utils_1.map(binObj),
        },
    }),
});
//# sourceMappingURL=controllers.js.map