var input = use('input')

var downkeys = {};

function keyname(key)
{
  var str = input.keyname(key);
  return str.toLowerCase();
}

function modstr(mod = input.keymod()) {
  var s = "";
  if (mod.ctrl) s += "C-";
  if (mod.alt) s += "M-";
  if (mod.super) s += "S-";
  return s;
}

prosperon.on('key_down', function key_down(e) {
  downkeys[e.key] = true;
  var emacs = modstr(e.mod) + keyname(e.key);
  if (e.repeat) player[0].raw_input(emacs, "rep");
  else player[0].raw_input(emacs, "pressed");
})

prosperon.on('quit', function() {
  os.exit(0);
})

prosperon.on('key_up', function key_up(e) {
  delete downkeys[e.key];
  var emacs = modstr(e.mod) + keyname(e.key);
  player[0].raw_input(emacs, "released");
})

prosperon.on('drop_file', function (path) {
  player[0].raw_input("drop", "pressed", path);
})

var mousepos = [0, 0];

prosperon.on('text_input', function (e) {
  player[0].raw_input("char", "pressed", e.text);
})

prosperon.on('mouse_motion', function (e)
{
  mousepos = e.pos;
  player[0].mouse_input("move", e.pos, e.d_pos);
})

prosperon.on('mouse_wheel', function mousescroll(e) {
  player[0].mouse_input(modstr() + "scroll", e.scroll);
})

prosperon.on('mouse_button_down', function(e) {
  player[0].mouse_input(modstr() + e.button, "pressed");  
  input.mouse.buttons[e.button] = true
})

prosperon.on('mouse_button_up', function(e) {
  player[0].mouse_input(modstr() + e.button, "released");
  input.mouse.buttons[e.button] = false
})

input.mouse = {};
input.mouse.screenpos = function mouse_screenpos() {
  return mousepos.slice();
};
input.mouse.worldpos = function mouse_worldpos() {
  return prosperon.camera.screen2world(mousepos);
};
input.mouse.viewpos = function mouse_viewpos()
{
  var world = input.mouse.worldpos();
  
  return mousepos.slice();
}
input.mouse.disabled = function mouse_disabled() {
  input.mouse_mode(1);
};
input.mouse.normal = function mouse_normal() {
  input.mouse_mode(0);
};
input.mouse.mode = function mouse_mode(m) {
  if (input.mouse.custom[m]) input.cursor_img(input.mouse.custom[m]);
  else input.mouse_cursor(m);
};
input.mouse.buttons = {
  0:false,
  1:false,
  2:false
}

input.mouse.set_custom_cursor = function mouse_cursor(img, mode = input.mouse.cursor.default) {
  if (!img) delete input.mouse.custom[mode];
  else {
    input.cursor_img(img);
    input.mouse.custom[mode] = img;
  }
};
input.mouse.doc = {};
input.mouse.doc.pos = "The screen position of the mouse.";
input.mouse.doc.worldpos = "The position in the game world of the mouse.";
input.mouse.disabled.doc = "Set the mouse to hidden. This locks it to the game and hides it, but still provides movement and click events.";
input.mouse.normal.doc = "Set the mouse to show again after hiding.";

input.keyboard = {};
input.keyboard.down = function (code) {
  if (typeof code == "number") return downkeys[code];
  if (typeof code == "string") return downkeys[code.toUpperCase().charCodeAt()] || downkeys[code.toLowerCase().charCodeAt()];
  return null;
};

input.print_pawn_kbm = function (pawn) {
  if (!("inputs" in pawn)) return;
  var str = "";
  for (var key in pawn.inputs) {
    if (!pawn.inputs[key].doc) continue;
    str += `${key} | ${pawn.inputs[key].doc}\n`;
  }
  return str;
};

var joysticks = {};

joysticks["wasd"] = {
  uy: "w",
  dy: "s",
  ux: "d",
  dx: "a",
};

input.procdown = function procdown() {
  for (var k in downkeys) player[0].raw_input(keyname(k), "down");

  for (var i in joysticks) {
    var joy = joysticks[i];
    var x = joy.ux - joy.dx;
    var y = joy.uy - joy.dy;
    player[0].joy_input(i, joysticks[i]);
  }
};

input.print_md_kbm = function print_md_kbm(pawn) {
  if (!("inputs" in pawn)) return;

  var str = "";
  str += "|control|description|\n|---|---|\n";

  for (var key in pawn.inputs) {
    str += `|${key}|${pawn.inputs[key].doc}|`;
    str += "\n";
  }

  return str;
};

input.has_bind = function (pawn, bind) {
  return typeof pawn.inputs?.[bind] == "function";
};

input.action = {
  add_new(name) {
    var action = Object.create(input.action);
    action.name = name;
    action.inputs = [];
    this.actions.push(action);

    return action;
  },
  actions: [],
};

input.tabcomplete = function tabcomplete(val, list) {
  if (!val) return val;
  list = filter(x => x.startsWith(val))

  if (list.length == 1) {
    return list[0];
  }

  var ret = null;
  var i = val.length;
  while (!ret && list.length != 0) {
    var char = list[0][i];
    if (
      !list.every(function (x) {
        return x[i] == char;
      })
    )
      ret = list[0].slice(0, i);
    else {
      i++;
      list = list.filter(x => x.length-1 > i)
    }
  }

  return ret ? ret : val;
};

/* May be a human player; may be an AI player */

/*
  'block' on a pawn's input blocks any input from reaching below for the 
*/

var Player = {
  players: [],
  input(fn, ...args) {
    this.pawns.forEach(x => x[fn]?.(...args));
  },

  mouse_input(type, ...args) {
    for (var pawn of [...this.pawns].reverse()) {
      if (typeof pawn.inputs?.mouse?.[type] == "function") {
        pawn.inputs.mouse[type].call(pawn, ...args);
        pawn.inputs.post?.call(pawn);
        if (!pawn.inputs.fallthru) return;
      }
    }
  },

  char_input(c) {
    for (var pawn of [...this.pawns].reverse()) {
      if (typeof pawn.inputs?.char == "function") {
        pawn.inputs.char.call(pawn, c);
        pawn.inputs.post?.call(pawn);
        if (!pawn.inputs.fallthru) return;
      }
    }
  },

  joy_input(name, joystick) {
    for (var pawn of [...this.pawns].reverse()) {
      if (!pawn.inputs) return;
      if (!pawn.inputs.joystick) return;
      if (!pawn.inputs.joystick[name]) return;

      var x = 0;
      if (input.keyboard.down(joystick.ux)) x++;
      if (input.keyboard.down(joystick.dx)) x--;
      var y = 0;
      if (input.keyboard.down(joystick.uy)) y++;
      if (input.keyboard.down(joystick.dy)) y--;

      pawn.inputs.joystick[name](x, y);
    }
  },

  raw_input(cmd, state, ...args) {
    for (var pawn of [...this.pawns].reverse()) {
      var inputs = pawn.inputs;

      if (!inputs[cmd]) {
        if (inputs.block) return;
        continue;
      }

      var fn = null;

      switch (state) {
        case "pressed":
          fn = inputs[cmd];
          break;
        case "rep":
          fn = inputs[cmd].rep ? inputs[cmd] : null;
          break;
        case "released":
          fn = inputs[cmd].released;
          break;
        case "down":
          if (typeof inputs[cmd].down == "function") fn = inputs[cmd].down;
          else if (inputs[cmd].down) fn = inputs[cmd];
      }

      var consumed = false;
      if (typeof fn == "function") {
        fn.call(pawn, ...args);
        consumed = true;
      }
      if (state == "released") inputs.release_post?.call(pawn);
      if (inputs.block) return;
      if (consumed) return;
    }
  },

  obj_controlled(obj) {
    for (var p in Player.players) {
      if (p.pawns.has(obj)) return true;
    }

    return false;
  },

  print_pawns() {
    [...this.pawns].reverse().forEach(x => log.console(x))
  },

  create() {
    var n = Object.create(this);
    n.pawns = new Set()
    n.gamepads = [];
    this.players.push(n);
    this[this.players.length - 1] = n;
    return n;
  },

  control(pawn) {
    if (!pawn)
      return

    if (!pawn.inputs)
      throw new Error("attempted to control a pawn without any input object.");

    this.pawns.add(pawn);
  },

  uncontrol(pawn) {
    this.pawns.delete(pawn)
  },
};

input.do_uncontrol = function input_do_uncontrol(pawn) {
  if (!pawn.inputs) return;
  Player.players.forEach(function (p) {
    p.pawns.delete(pawn)
  });
};

//for (var i = 0; i < 4; i++)
Player.create();

Player.control.doc = "Control a provided object, if the object has an 'inputs' object.";
Player.uncontrol.doc = "Uncontrol a previously controlled object.";
Player.print_pawns.doc = "Print out a list of the current pawn control stack.";
Player.doc = {};
Player.doc.players = "A list of current players.";

var player = Player;

input.player = Player

return input
