// vim:set noexpandtab:
/**************
 SYSTEM INCLUDES
**************/
var http = require("http");
var reload = require("reload");
var sanitizer = require("sanitizer");
var compression = require("compression");
var express = require("express");
var conf = require("./config.js").server;
var headerBarUrl = require("./config.js").headerBarUrl;
var logoUrl = require("./config.js").logoUrl;

/**************
 LOCAL INCLUDES
**************/
var rooms = require("./lib/rooms.js");
var data = require("./lib/data.js").db;

/**************
 GLOBALS
**************/
//Map of sids to user_names
var sids_to_user_names = [];

/**************
 SETUP EXPRESS
**************/
var app = express();
var router = express.Router();

app.use(compression());
app.use(conf.baseurl, router);

router.use(express.static(__dirname + "/node_modules"));
router.use(express.static(__dirname + "/client"));

var server = http.createServer(app);

// Reload code here
reload(app)
  .then(function (reloadReturned) {
    // reloadReturned is documented in the returns API in the README

    // Reload started, start web server
    server.listen(conf.port, function () {
      console.log("Web server available on http://127.0.0.1:" + conf.port);
    });
  })
  .catch(function (err) {
    console.error(
      "Reload could not start, could not start server/sample app",
      err
    );
  });

/**************
 SETUP Socket.IO
**************/
var io = require("socket.io")(server, {
  path: conf.baseurl == "/" ? "" : conf.baseurl + "/socket.io",
  cookie: false
});

/**************
 ROUTES
**************/
router.get("/", function (req, res) {
  //console.log(req.header('host'));
  url = req.header("host") + req.baseUrl;
  var connected = io.sockets.connected;
  clientsCount = Object.keys(connected).length;
  res.render("home.jade", {
    url: url,
    headerBarUrl: headerBarUrl,
    logoUrl: logoUrl,
    connected: clientsCount,
    home: true,
  });
});

router.get("/demo", function (req, res) {
  url = req.header("host") + req.baseUrl;
  res.render("index.jade", {
    pageTitle: "Post-it - demo",
    headerBarUrl: headerBarUrl,
    logoUrl: logoUrl,
    url: url,
    demo: true,
  });
});

router.get("/:id", function (req, res) {
  url = req.header("host") + req.baseUrl;
  res.render("index.jade", {
    pageTitle: "Post-it - " + req.params.id,
    headerBarUrl: headerBarUrl,
    logoUrl: logoUrl,
    url: url,
  });
});

router.get("/stats", function (req, res) {
  console.log("TODO: stats");
});

/**************
 SOCKET.I0
**************/
//sanitizes text
function scrub(text) {
  if (typeof text != "undefined" && text !== null) {
    //clip the string if it is too long
    if (text.length > 65535) {
      text = text.substr(0, 65535);
    }

    return sanitizer.sanitize(text);
  } else {
    return null;
  }
}

io.sockets.on("connection", function (client) {
  client.on("message", function (message) {
    //console.log(message.action + " -- " + sys.inspect(message.data) );

    var clean_data = {};
    var clean_message = {};
    var message_out = {};

    if (!message.action) return;

    switch (message.action) {
      case "initializeMe":
        initClient(client);
        break;

      case "joinRoom":
        joinRoom(client, message.data, function (clients) {
          client.json.send({ action: "roomAccept", data: "" });
        });

        break;

      case "moveCard":
        //report to all other browsers
        message_out = {
          action: message.action,
          data: {
            id: scrub(message.data.id),
            position: {
              left: scrub(message.data.position.left),
              top: scrub(message.data.position.top),
            },
          },
        };

        broadcastToRoom(client, message_out);

        // console.log("-----" + message.data.id);
        // console.log(JSON.stringify(message.data));

        getRoom(client, function (room) {
          db.cardSetXY(
            room,
            message.data.id,
            message.data.position.left,
            message.data.position.top
          );
        });

        break;

      case "createCard":
        data = message.data;
        clean_data = {};
        clean_data.text = scrub(data.text);
        clean_data.id = scrub(data.id);
        clean_data.x = scrub(data.x);
        clean_data.y = scrub(data.y);
        clean_data.rot = scrub(data.rot);
        clean_data.colour = scrub(data.colour);

        getRoom(client, function (room) {
          createCard(
            room,
            clean_data.id,
            clean_data.text,
            clean_data.x,
            clean_data.y,
            clean_data.rot,
            clean_data.colour
          );
        });

        message_out = {
          action: "createCard",
          data: clean_data,
        };

        //report to all other browsers
        broadcastToRoom(client, message_out);
        break;

      case "editCard":
        clean_data = {};
        clean_data.value = scrub(message.data.value);
        clean_data.id = scrub(message.data.id);

        //send update to database
        getRoom(client, function (room) {
          db.cardEdit(room, clean_data.id, clean_data.value);
        });

        message_out = {
          action: "editCard",
          data: clean_data,
        };

        broadcastToRoom(client, message_out);

        break;

      case "deleteCard":
        clean_message = {
          action: "deleteCard",
          data: { id: scrub(message.data.id) },
        };

        getRoom(client, function (room) {
          db.deleteCard(room, clean_message.data.id);
        });

        //report to all other browsers
        broadcastToRoom(client, clean_message);

        break;

      case "createColumn":
        clean_message = { data: scrub(message.data) };

        getRoom(client, function (room) {
          db.createColumn(room, clean_message.data, function () {});
        });

        broadcastToRoom(client, clean_message);

        break;

      case "deleteColumn":
        getRoom(client, function (room) {
          db.deleteColumn(room);
        });
        broadcastToRoom(client, { action: "deleteColumn" });

        break;

      case "updateColumns":
        var columns = message.data;

        if (!(columns instanceof Array)) break;

        var clean_columns = [];

        for (var i in columns) {
          clean_columns[i] = scrub(columns[i]);
        }
        getRoom(client, function (room) {
          db.setColumns(room, clean_columns);
        });

        broadcastToRoom(client, {
          action: "updateColumns",
          data: clean_columns,
        });

        break;

      case "changeTheme":
        clean_message = {};
        clean_message.data = scrub(message.data);

        getRoom(client, function (room) {
          db.setTheme(room, clean_message.data);
        });

        clean_message.action = "changeTheme";

        broadcastToRoom(client, clean_message);
        break;

      case "setUserName":
        clean_message = {};

        clean_message.data = scrub(message.data);

        setUserName(client, clean_message.data);

        var msg = {};
        msg.action = "nameChangeAnnounce";
        msg.data = { sid: client.id, user_name: clean_message.data };
        broadcastToRoom(client, msg);
        break;

      case "addSticker":
        var cardId = scrub(message.data.cardId);
        var stickerId = scrub(message.data.stickerId);

        getRoom(client, function (room) {
          db.addSticker(room, cardId, stickerId);
        });

        broadcastToRoom(client, {
          action: "addSticker",
          data: { cardId: cardId, stickerId: stickerId },
        });
        break;

      case "setBoardSize":
        var size = {};
        size.width = scrub(message.data.width);
        size.height = scrub(message.data.height);

        getRoom(client, function (room) {
          db.setBoardSize(room, size);
        });

        broadcastToRoom(client, { action: "setBoardSize", data: size });
        break;

      case "exportTxt":
        exportBoard("txt", client, message.data);
        break;

      case "exportCsv":
        exportBoard("csv", client, message.data);
        break;

      case "exportJson":
        exportJson(client, message.data);
        break;

      case "importJson":
        importJson(client, message.data);
        break;

      case "createRevision":
        createRevision(client, message.data);
        break;

      case "deleteRevision":
        deleteRevision(client, message.data);
        break;

      case "exportRevision":
        exportRevision(client, message.data);
        break;

      default:
        //console.log('unknown action');
        break;
    }
  });

  client.on("disconnect", function () {
    leaveRoom(client);
  });

  //tell all others that someone has connected
  //client.broadcast('someone has connected');
});

/**************
 FUNCTIONS
**************/
function initClient(client) {
  //console.log ('initClient Started');
  getRoom(client, function (room) {
    db.getAllCards(room, function (cards) {
      client.json.send({
        action: "initCards",
        data: cards,
      });
    });

    db.getAllColumns(room, function (columns) {
      client.json.send({
        action: "initColumns",
        data: columns,
      });
    });

    db.getRevisions(room, function (revisions) {
      client.json.send({
        action: "initRevisions",
        data: revisions !== null ? Object.keys(revisions) : new Array(),
      });
    });

    db.getTheme(room, function (theme) {
      if (theme === null) theme = "bigcards";

      client.json.send({
        action: "changeTheme",
        data: theme,
      });
    });

    db.getBoardSize(room, function (size) {
      if (size !== null) {
        client.json.send({
          action: "setBoardSize",
          data: size,
        });
      }
    });

    roommates_clients = rooms.room_clients(room);
    roommates = [];

    var j = 0;
    for (var i in roommates_clients) {
      if (roommates_clients[i].id != client.id) {
        roommates[j] = {
          sid: roommates_clients[i].id,
          user_name: sids_to_user_names[roommates_clients[i].id],
        };
        j++;
      }
    }

    //console.log('initialusers: ' + roommates);
    client.json.send({
      action: "initialUsers",
      data: roommates,
    });
  });
}

function joinRoom(client, room, successFunction) {
  var msg = {};
  msg.action = "join-announce";
  msg.data = { sid: client.id, user_name: client.user_name };

  rooms.add_to_room_and_announce(client, room, msg);
  successFunction();
}

function leaveRoom(client) {
  //console.log (client.id + ' just left');
  var msg = {};
  msg.action = "leave-announce";
  msg.data = { sid: client.id };
  rooms.remove_from_all_rooms_and_announce(client, msg);

  delete sids_to_user_names[client.id];
}

function broadcastToRoom(client, message) {
  rooms.broadcast_to_roommates(client, message);
}

//----------------CARD FUNCTIONS
function createCard(room, id, text, x, y, rot, colour) {
  var card = {
    id: id,
    colour: colour,
    rot: rot,
    x: x,
    y: y,
    text: text,
    sticker: null,
  };

  db.createCard(room, id, card);
}

function roundRand(max) {
  return Math.floor(Math.random() * max);
}

//------------ROOM STUFF
// Get Room name for the given Session ID
function getRoom(client, callback) {
  room = rooms.get_room(client);
  //console.log( 'client: ' + client.id + " is in " + room);
  callback(room);
}

function setUserName(client, name) {
  client.user_name = name;
  sids_to_user_names[client.id] = name;
  //console.log('sids to user names: ');
  console.dir(sids_to_user_names);
}

function cleanAndInitializeDemoRoom() {
  // DUMMY DATA
  db.clearRoom("/demo", function () {
    db.createColumn("/demo", "Pas commencé");
    db.createColumn("/demo", "Commencé");
    db.createColumn("/demo", "En test");
    db.createColumn("/demo", "Validation");
    db.createColumn("/demo", "Terminé");

    createCard(
      "/demo",
      "card1",
      "Salut, c'est fun",
      roundRand(600),
      roundRand(300),
      Math.random() * 10 - 5,
      "yellow"
    );
    createCard(
      "/demo",
      "card2",
      "Salut, c'est une nouvelle histoire.",
      roundRand(600),
      roundRand(300),
      Math.random() * 10 - 5,
      "white"
    );
    createCard(
      "/demo",
      "card3",
      ".",
      roundRand(600),
      roundRand(300),
      Math.random() * 10 - 5,
      "blue"
    );
    createCard(
      "/demo",
      "card4",
      ".",
      roundRand(600),
      roundRand(300),
      Math.random() * 10 - 5,
      "green"
    );

    createCard(
      "/demo",
      "card5",
      "Salut, c'est fun",
      roundRand(600),
      roundRand(300),
      Math.random() * 10 - 5,
      "yellow"
    );
    createCard(
      "/demo",
      "card6",
      "Salut, c'est un nouveau mémo.",
      roundRand(600),
      roundRand(300),
      Math.random() * 10 - 5,
      "yellow"
    );
    createCard(
      "/demo",
      "card7",
      ".",
      roundRand(600),
      roundRand(300),
      Math.random() * 10 - 5,
      "blue"
    );
    createCard(
      "/demo",
      "card8",
      ".",
      roundRand(600),
      roundRand(300),
      Math.random() * 10 - 5,
      "green"
    );
  });
}

// Export board in txt or csv
function exportBoard(format, client, data) {
  var result = new Array();
  getRoom(client, function (room) {
    db.getAllCards(room, function (cards) {
      db.getAllColumns(room, function (columns) {
        var text = new Array();
        var cols = {};
        if (columns.length > 0) {
          for (var i = 0; i < columns.length; i++) {
            cols[columns[i]] = new Array();
            for (var j = 0; j < cards.length; j++) {
              if (i === 0) {
                if (cards[j]["x"] < (i + 1) * data) {
                  cols[columns[i]].push(cards[j]);
                }
              } else if (i + 1 === columns.length) {
                if (cards[j]["x"] >= i * data) {
                  cols[columns[i]].push(cards[j]);
                }
              } else if (
                cards[j]["x"] >= i * data &&
                cards[j]["x"] < (i + 1) * data
              ) {
                cols[columns[i]].push(cards[j]);
              }
            }
            cols[columns[i]].sort(function (a, b) {
              if (a["y"] === b["y"]) {
                return a["x"] - b["x"];
              } else {
                return a["y"] - b["y"];
              }
            });
          }
          if (format === "txt") {
            for (var i = 0; i < columns.length; i++) {
              if (i === 0) {
                text.push("# " + columns[i]);
              } else {
                text.push("\n# " + columns[i]);
              }
              for (var j = 0; j < cols[columns[i]].length; j++) {
                text.push("- " + cols[columns[i]][j]["text"]);
              }
            }
          } else if (format === "csv") {
            var max = 0;
            var line = new Array();
            var patt_vuln = new RegExp("^[=+-@]");
            for (var i = 0; i < columns.length; i++) {
              if (cols[columns[i]].length > max) {
                max = cols[columns[i]].length;
              }
              var val = columns[i].replace(/"/g, '""');
              if (patt_vuln.test(val)) {
                // prevent CSV Formula Injection
                var val = "'" + val;
              }
              line.push('"' + val + '"');
            }
            text.push(line.join(","));
            for (var j = 0; j < max; j++) {
              line = new Array();
              for (var i = 0; i < columns.length; i++) {
                var val =
                  cols[columns[i]][j] !== undefined
                    ? cols[columns[i]][j]["text"].replace(/"/g, '""')
                    : "";
                if (patt_vuln.test(val)) {
                  // prevent CSV Formula Injection
                  var val = "'" + val;
                }
                line.push('"' + val + '"');
              }
              text.push(line.join(","));
            }
          }
        } else {
          for (var j = 0; j < cards.length; j++) {
            if (format === "txt") {
              text.push("- " + cards[j]["text"]);
            } else if (format === "csv") {
              text.push('"' + cards[j]["text"].replace(/"/g, '""') + '"\n');
            }
          }
        }
        var result;
        if (format === "txt" || format === "csv") {
          result = text.join("\n");
        } else if (format === "json") {
          result = JSON.stringify(cols);
        }
        client.json.send({
          action: "export",
          data: {
            filename: room.replace("/", "") + "." + format,
            text: result,
          },
        });
      });
    });
  });
}

// Export board in json, suitable for import
function exportJson(client, data) {
  var result = new Array();
  getRoom(client, function (room) {
    db.getAllCards(room, function (cards) {
      db.getAllColumns(room, function (columns) {
        db.getTheme(room, function (theme) {
          db.getBoardSize(room, function (size) {
            if (theme === null) theme = "bigcards";
            if (size === null)
              size = { width: data.width, height: data.height };
            result = JSON.stringify({
              cards: cards,
              columns: columns,
              theme: theme,
              size: size,
            });
            client.json.send({
              action: "export",
              data: {
                filename: room.replace("/", "") + ".json",
                text: result,
              },
            });
          });
        });
      });
    });
  });
}

// Import board from json
function importJson(client, data) {
  getRoom(client, function (room) {
    db.clearRoom(room, function () {
      db.getAllCards(room, function (cards) {
        for (var i = 0; i < cards.length; i++) {
          db.deleteCard(room, cards[i].id);
        }

        cards = data.cards;
        var cards2 = new Array();
        for (var i = 0; i < cards.length; i++) {
          var card = cards[i];
          if (
            card.id !== undefined &&
            card.colour !== undefined &&
            card.rot !== undefined &&
            card.x !== undefined &&
            card.y !== undefined &&
            card.text !== undefined &&
            card.sticker !== undefined
          ) {
            var c = {
              id: card.id,
              colour: card.colour,
              rot: card.rot,
              x: card.x,
              y: card.y,
              text: scrub(card.text),
              sticker: card.sticker,
            };
            db.createCard(room, c.id, c);
            cards2.push(c);
          }
        }
        var msg = { action: "initCards", data: cards2 };
        broadcastToRoom(client, msg);
        client.json.send(msg);
      });

      db.getAllColumns(room, function (columns) {
        for (var i = 0; i < columns.length; i++) {
          db.deleteColumn(room);
        }

        columns = data.columns;
        var columns2 = new Array();
        for (var i = 0; i < columns.length; i++) {
          var column = scrub(columns[i]);
          if (typeof column === "string") {
            db.createColumn(room, column);
            columns2.push(column);
          }
        }
        msg = { action: "initColumns", data: columns2 };
        broadcastToRoom(client, msg);
        client.json.send(msg);
      });

      var size = data.size;
      if (size.width !== undefined && size.height !== undefined) {
        size = { width: scrub(size.width), height: scrub(size.height) };
        db.setBoardSize(room, size);
        msg = { action: "setBoardSize", data: size };
        broadcastToRoom(client, msg);
        client.json.send(msg);
      }

      data.theme = scrub(data.theme);
      if (data.theme === "smallcards" || data.theme === "bigcards") {
        db.setTheme(room, data.theme);
        msg = { action: "changeTheme", data: data.theme };
        broadcastToRoom(client, msg);
        client.json.send(msg);
      }
    });
  });
}
//

function createRevision(client, data) {
  var result = new Array();
  getRoom(client, function (room) {
    db.getAllCards(room, function (cards) {
      db.getAllColumns(room, function (columns) {
        db.getTheme(room, function (theme) {
          db.getBoardSize(room, function (size) {
            if (theme === null) theme = "bigcards";
            if (size === null)
              size = { width: data.width, height: data.height };
            result = {
              cards: cards,
              columns: columns,
              theme: theme,
              size: size,
            };
            var timestamp = Date.now();
            db.getRevisions(room, function (revisions) {
              if (revisions === null) revisions = {};
              revisions[timestamp + ""] = result;
              db.setRevisions(room, revisions);
              msg = { action: "addRevision", data: timestamp };
              broadcastToRoom(client, msg);
              client.json.send(msg);
            });
          });
        });
      });
    });
  });
}

function deleteRevision(client, timestamp) {
  getRoom(client, function (room) {
    db.getRevisions(room, function (revisions) {
      if (revisions !== null && revisions[timestamp + ""] !== undefined) {
        delete revisions[timestamp + ""];
        db.setRevisions(room, revisions);
      }
      msg = { action: "deleteRevision", data: timestamp };
      broadcastToRoom(client, msg);
      client.json.send(msg);
    });
  });
}

function exportRevision(client, timestamp) {
  getRoom(client, function (room) {
    db.getRevisions(room, function (revisions) {
      if (revisions !== null && revisions[timestamp + ""] !== undefined) {
        client.json.send({
          action: "export",
          data: {
            filename: room.replace("/", "") + "-" + timestamp + ".json",
            text: JSON.stringify(revisions[timestamp + ""]),
          },
        });
      } else {
        client.json.send({
          action: "message",
          data: "Unable to find revision " + timestamp + ".",
        });
      }
    });
  });
}
/**************
 SETUP DATABASE ON FIRST RUN
**************/
// (runs only once on startup)
var db = new data(function () {
  cleanAndInitializeDemoRoom();
});