// vim:set noexpandtab:
/**************
 SYSTEM INCLUDES
**************/
var http = require("http")
// var sys = require('sys');
var async = require("async")
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 + "/client"))

var server = require("http").Server(app)
server.listen(conf.port)

console.log("Server running at http://127.0.0.1:" + conf.port + "/")

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

/**************
 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
  })
})

router.get("/demo", function (req, res) {
  res.render("index.jade", {
    pageTitle: "Post-it - demo",
	headerBarUrl: headerBarUrl,
    logoUrl: logoUrl,
    demo: true
  })
})

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

/**************
 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()
})