// Run on Node.js.

const V3 = (x, y, z) => ({x, y, z});

const Cube = () => {
  var id = 0;
  var cube = [];
  for (var x = -1; x <= 1; ++x) {
    for (var y = -1; y <= 1; ++y) {
      for (var z = -1; z <= 1; ++z) {
        if (x === -1) cube.push([{x:-2, y   , z   }, id++]);
        if (y === -1) cube.push([{x   , y:-2, z   }, id++]);
        if (z === -1) cube.push([{x   , y   , z:-2}, id++]);
        if (x ===  1) cube.push([{x: 2, y   , z   }, id++]);
        if (y ===  1) cube.push([{x   , y: 2, z   }, id++]);
        if (z ===  1) cube.push([{x   , y   , z: 2}, id++]);
      };
    };
  };
  return cube;
};

// Rotates
const rotate = (axis, pos, clock, cube) => {
  const rotateVec = (axis, clock, vec) => {
    var {x, y, z} = vec;
    switch (axis) {
      case "x": return clock ? {x, y:z, z:-y} : {x, y:-z, z:y}; 
      case "y": return clock ? {x:z, y, z:-x} : {x:-z, y, z:x}; 
      case "z": return clock ? {x:y, y:-x, z} : {x:-y, y:x, z}; 
    }
  };
  return cube.map(([{x,y,z},id]) => {
    const onPos
      =  axis === "x" && x === pos
      || axis === "y" && y === pos
      || axis === "z" && z === pos;
    return onPos ? [rotateVec(axis,clock,{x,y,z}),id] : [{x,y,z},id];
  });
};

// Displays cube in a pretty format
const show = (cube, axis, pos) => {
  var ps = ({x,y,z}) => x+"|"+y+"|"+z;
  var al = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@$";
  var at = cube.reduce((m,[pos,id]) => (m[ps(pos)] = id, m), {});
  var map = "";
  for (var z = 2; z >= -2; --z) {
    map += "----- z="+z+"\n"
    var layer = "";
    for (var y = -2; y <= 2; ++y) {
      var line = "";
      for (var x = -2; x <= 2; ++x) {
        var id = at[ps({x,y,z})];
        var bold
          =  axis === "x" && pos === x
          || axis === "y" && pos === y
          || axis === "z" && pos === z;
        line += id >= 0 ? "\x1b["+(bold?"1":"2")+"m"+al[id]+"\x1b[0m" : " ";
      };
      layer = axis === "y" ? line+"\n"+layer : layer+line+"\n";
    };
    map += layer;
  };
  map += "~~~~~"
  return map;
};








// Interact

var axis  = "z";
var pos   = 0;
var cube = Cube();

const display = () => {
  process.stdout.write('\033c')
  console.log("axis = <- " + JSON.stringify(axis) + " ->");
  console.log("pos  = \\/ " + (pos>=0?" ":"") + pos + "  /\\");
  console.log("");
  console.log(show(cube,axis,pos));
  console.log("");
  console.log("Press D / F to rotate.");
  console.log("Press arrows to change selection.");
  console.log("Press Q to quit.");
};

const interact = () => {
  const readline = require('readline');
  readline.emitKeypressEvents(process.stdin);
  process.stdin.setRawMode(true);
  process.stdin.on('keypress', (str, key) => {
    if (key.ctrl && key.name === "c" || key.name === "q" || key.name === "escape") {
      process.exit();
    } else {
      switch (key.name) {
        case "down":
          pos = pos > -2 ? pos - 1 : pos;
          break;
        case "up":
          pos = pos < 2 ? pos + 1 : pos;
          break;
        case "right":
          axis = ({x:"y",y:"z",z:"z"})[axis];
          break;
        case "left":
          axis = ({x:"x",y:"x",z:"y"})[axis];
          break;
        case "f":
          cube = rotate(axis, pos, 0, cube);
          break;
        case "d":
          cube = rotate(axis, pos, 1, cube);
          break;
      };
      display();
    }
  });
};

display();
interact();
