import {log} from 'log';

const endpoints = {
    LATENCY_CHECK: 3,
    INIT: 4,
    INIT_WEBRTC: 100,
    OFFER: 101,
    ANSWER: 102,
    ICE_CANDIDATE: 103,
    GAME_START: 104,
    GAME_QUIT: 105,
    GAME_SAVE: 106,
    GAME_LOAD: 107,
    GAME_SET_PLAYER_INDEX: 108,
    GAME_RECORDING: 110,
    GET_WORKER_LIST: 111,
    GAME_ERROR_NO_FREE_SLOTS: 112,
    GAME_RESET: 113,

    APP_VIDEO_CHANGE: 150,
}

let transport = {
    send: (packet) => {
        log.warn('Default transport is used! Change it with the api.transport variable.', packet)
    },
    keyboard: (packet) => {
        log.warn('Default transport is used! Change it with the api.transport variable.', packet)
    },
    mouse: (packet) => {
        log.warn('Default transport is used! Change it with the api.transport variable.', packet)
    }
}

const packet = (type, payload, id) => {
    const packet = {t: type}
    if (id !== undefined) packet.id = id
    if (payload !== undefined) packet.p = payload
    transport.send(packet)
}

const decodeBytes = (b) => String.fromCharCode.apply(null, new Uint8Array(b))

const keyboardPress = (() => {
    // 0 1 2 3 4 5 6
    // [CODE ] P MOD
    const buffer = new ArrayBuffer(7)
    const dv = new DataView(buffer)

    return (pressed = false, e) => {
        if (e.repeat) return // skip pressed key events

        const key = libretro.mod
        let code = libretro.map('', e.code)
        let shift = e.shiftKey

        // a special Esc for &$&!& Firefox
        if (shift && code === 96) {
            code = 27
            shift = false
        }

        const mod = 0
            | (e.altKey && key.ALT)
            | (e.ctrlKey && key.CTRL)
            | (e.metaKey && key.META)
            | (shift && key.SHIFT)
            | (e.getModifierState('NumLock') && key.NUMLOCK)
            | (e.getModifierState('CapsLock') && key.CAPSLOCK)
            | (e.getModifierState('ScrollLock') && key.SCROLLOCK)
        dv.setUint32(0, code)
        dv.setUint8(4, +pressed)
        dv.setUint16(5, mod)
        transport.keyboard(buffer)
    }
})()

const mouse = {
    MOVEMENT: 0,
    BUTTONS: 1
}

const mouseMove = (() => {
    // 0 1 2 3 4
    // T DX  DY
    const buffer = new ArrayBuffer(5)
    const dv = new DataView(buffer)

    return (dx = 0, dy = 0) => {
        dv.setUint8(0, mouse.MOVEMENT)
        dv.setInt16(1, dx)
        dv.setInt16(3, dy)
        transport.mouse(buffer)
    }
})()

const mousePress = (() => {
    // 0 1
    // T B
    const buffer = new ArrayBuffer(2)
    const dv = new DataView(buffer)

    // 0: Main button pressed, usually the left button or the un-initialized state
    // 1: Auxiliary button pressed, usually the wheel button or the middle button (if present)
    // 2: Secondary button pressed, usually the right button
    // 3: Fourth button, typically the Browser Back button
    // 4: Fifth button, typically the Browser Forward button

    const b2r = [1, 4, 2, 0, 0] // browser mouse button to retro button
    // assumed that only one button pressed / released

    return (button = 0, pressed = false) => {
        dv.setUint8(0, mouse.BUTTONS)
        dv.setUint8(1, pressed ? b2r[button] : 0)
        transport.mouse(buffer)
    }
})()


const libretro = function () {// RETRO_KEYBOARD
    const retro = {
        '': 0,
        'Unidentified': 0,
        'Unknown': 0, // ???
        'First': 0, // ???
        'Backspace': 8,
        'Tab': 9,
        'Clear': 12,
        'Enter': 13, 'Return': 13,
        'Pause': 19,
        'Escape': 27,
        'Space': 32,
        'Exclaim': 33,
        'Quotedbl': 34,
        'Hash': 35,
        'Dollar': 36,
        'Ampersand': 38,
        'Quote': 39,
        'Leftparen': 40, '(': 40,
        'Rightparen': 41, ')': 41,
        'Asterisk': 42,
        'Plus': 43,
        'Comma': 44,
        'Minus': 45,
        'Period': 46,
        'Slash': 47,
        'Digit0': 48,
        'Digit1': 49,
        'Digit2': 50,
        'Digit3': 51,
        'Digit4': 52,
        'Digit5': 53,
        'Digit6': 54,
        'Digit7': 55,
        'Digit8': 56,
        'Digit9': 57,
        'Colon': 58, ':': 58,
        'Semicolon': 59, ';': 59,
        'Less': 60, '<': 60,
        'Equal': 61, '=': 61,
        'Greater': 62, '>': 62,
        'Question': 63, '?': 63,
        // RETROK_AT = 64,
        'BracketLeft': 91, '[': 91,
        'Backslash': 92, '\\': 92,
        'BracketRight': 93, ']': 93,
        // RETROK_CARET = 94,
        // RETROK_UNDERSCORE = 95,
        'Backquote': 96, '`': 96,
        'KeyA': 97,
        'KeyB': 98,
        'KeyC': 99,
        'KeyD': 100,
        'KeyE': 101,
        'KeyF': 102,
        'KeyG': 103,
        'KeyH': 104,
        'KeyI': 105,
        'KeyJ': 106,
        'KeyK': 107,
        'KeyL': 108,
        'KeyM': 109,
        'KeyN': 110,
        'KeyO': 111,
        'KeyP': 112,
        'KeyQ': 113,
        'KeyR': 114,
        'KeyS': 115,
        'KeyT': 116,
        'KeyU': 117,
        'KeyV': 118,
        'KeyW': 119,
        'KeyX': 120,
        'KeyY': 121,
        'KeyZ': 122,
        '{': 123,
        '|': 124,
        '}': 125,
        'Tilde': 126, '~': 126,
        'Delete': 127,

        'Numpad0': 256,
        'Numpad1': 257,
        'Numpad2': 258,
        'Numpad3': 259,
        'Numpad4': 260,
        'Numpad5': 261,
        'Numpad6': 262,
        'Numpad7': 263,
        'Numpad8': 264,
        'Numpad9': 265,
        'NumpadDecimal': 266,
        'NumpadDivide': 267,
        'NumpadMultiply': 268,
        'NumpadSubtract': 269,
        'NumpadAdd': 270,
        'NumpadEnter': 271,
        'NumpadEqual': 272,

        'ArrowUp': 273,
        'ArrowDown': 274,
        'ArrowRight': 275,
        'ArrowLeft': 276,
        'Insert': 277,
        'Home': 278,
        'End': 279,
        'PageUp': 280,
        'PageDown': 281,

        'F1': 282,
        'F2': 283,
        'F3': 284,
        'F4': 285,
        'F5': 286,
        'F6': 287,
        'F7': 288,
        'F8': 289,
        'F9': 290,
        'F10': 291,
        'F11': 292,
        'F12': 293,
        'F13': 294,
        'F14': 295,
        'F15': 296,

        'NumLock': 300,
        'CapsLock': 301,
        'ScrollLock': 302,
        'ShiftRight': 303,
        'ShiftLeft': 304,
        'ControlRight': 305,
        'ControlLeft': 306,
        'AltRight': 307,
        'AltLeft': 308,
        'MetaRight': 309,
        'MetaLeft': 310,
        // RETROK_LSUPER = 311,
        // RETROK_RSUPER = 312,
        // RETROK_MODE = 313,
        // RETROK_COMPOSE = 314,

        // RETROK_HELP = 315,
        // RETROK_PRINT = 316,
        // RETROK_SYSREQ = 317,
        // RETROK_BREAK = 318,
        // RETROK_MENU = 319,
        'Power': 320,
        // RETROK_EURO = 321,
        // RETROK_UNDO = 322,
        // RETROK_OEM_102 = 323,
    }

    const retroMod = {
        NONE: 0x0000,
        SHIFT: 0x01,
        CTRL: 0x02,
        ALT: 0x04,
        META: 0x08,
        NUMLOCK: 0x10,
        CAPSLOCK: 0x20,
        SCROLLOCK: 0x40,
    }

    const _map = (key = '', code = '') => {
        return retro[code] || retro[key] || 0
    }

    return {
        map: _map,
        mod: retroMod,
    }
}()

/**
 * Server API.
 *
 * Requires the actual api.transport implementation.
 */
export const api = {
    set transport(t) {
        transport = t;
    },
    endpoint: endpoints,
    decode: (b) => JSON.parse(decodeBytes(b)),
    server: {
        initWebrtc: () => packet(endpoints.INIT_WEBRTC),
        sendIceCandidate: (candidate) => packet(endpoints.ICE_CANDIDATE, btoa(JSON.stringify(candidate))),
        sendSdp: (sdp) => packet(endpoints.ANSWER, btoa(JSON.stringify(sdp))),
        latencyCheck: (id, list) => packet(endpoints.LATENCY_CHECK, list, id),
        getWorkerList: () => packet(endpoints.GET_WORKER_LIST),
    },
    game: {
        input: {
            keyboard: {
                press: keyboardPress,
            },
            mouse: {
                move: mouseMove,
                press: mousePress,
            }
        },
        load: () => packet(endpoints.GAME_LOAD),
        reset: (roomId) => packet(endpoints.GAME_RESET, {room_id: roomId}),
        save: () => packet(endpoints.GAME_SAVE),
        setPlayerIndex: (i) => packet(endpoints.GAME_SET_PLAYER_INDEX, i),
        start: (game, roomId, record, recordUser, player) => packet(endpoints.GAME_START, {
            game_name: game,
            room_id: roomId,
            player_index: player,
            record: record,
            record_user: recordUser,
        }),
        toggleRecording: (active = false, userName = '') =>
            packet(endpoints.GAME_RECORDING, {active: active, user: userName}),
        quit: (roomId) => packet(endpoints.GAME_QUIT, {room_id: roomId}),
    }
}
