define([
    '/api/config?cb=' + Math.random().toString(16).substring(2),
    '/common/messages.js',
    '/common/crypto.js',
    '/common/toolbar.js',
    '/common/chainpad.js',
    '/bower_components/jquery/dist/jquery.min.js',
    '/common/otaml.js',
    '/customize/sheet.js'
], function (Config, Messages, Crypto, Toolbar) {
    var $ = window.jQuery;
    var ChainPad = window.ChainPad;
    var Otaml = window.Otaml;

    var module = { exports: {} };

    var sheetToJson = function (ifrWindow) {
        var xx = ifrWindow.sh[0].jS;
        var m = [];
        for (var i = 0; i < xx.spreadsheets.length; i++) {
          m[i]=[];
          var sheet = xx.spreadsheets[i];
          for (var j = 1; j < sheet.length; j++) {
            m[i][j]=[];
            var row = sheet[j];
            for (var k = 1; k < row.length; k++) {
              var col = row[k];
              m[i][j][k] = { value: col.value, formula: col.formula };
            }
          }
        }
        return m;
    };

    var jsonToSheet = function (ifrWindow, json) {
        var xx = ifrWindow.sh[0].jS;
        for (var i = 0; i < xx.spreadsheets.length; i++) {
          var sheet = xx.spreadsheets[i];
          for (var j = 1; j < sheet.length; j++) {
            var row = sheet[j];
            for (var k = 1; k < row.length; k++) {
              var col = row[k];
              var jcol = json[i][j][k];
              if (jcol.value === col.value && jcol.formula === col.formula) { continue; }
              col.value = jcol.value;
              col.formula = jcol.formula;
              col.displayValue();
            }
          }
        }
    };

    var applyChange = function(ctx, oldval, newval) {
      if (oldval === newval) {
          return;
      }

      var commonStart = 0;
      while (oldval.charAt(commonStart) === newval.charAt(commonStart)) {
        commonStart++;
      }

      var commonEnd = 0;
      while (oldval.charAt(oldval.length - 1 - commonEnd) === newval.charAt(newval.length - 1 - commonEnd) &&
          commonEnd + commonStart < oldval.length && commonEnd + commonStart < newval.length) {
        commonEnd++;
      }

      if (oldval.length !== commonStart + commonEnd) {
        ctx.remove(commonStart, oldval.length - commonStart - commonEnd);
      }
      if (newval.length !== commonStart + commonEnd) {
        ctx.insert(commonStart, newval.slice(commonStart, newval.length - commonEnd));
      }
    };

    $(function () {

        if (window.location.href.indexOf('#') === -1) {
            window.location.href = window.location.href + '#' + Crypto.genKey();
        }
        $(window).on('hashchange', function() {
            window.location.reload();
        });

        var $sheetJson = $('#sheet-json');
        var ifrw = $('iframe')[0].contentWindow;
        var sheetEvent = function (realtime) {
            var sheetJson = JSON.stringify(sheetToJson(ifrw));
            applyChange(realtime, realtime.getUserDoc(), sheetJson);
            $sheetJson.text(sheetJson);
        };

        var eventPending = false;
        var realtimeEvent = function (realtime) {
            if (eventPending) { return; }
            eventPending = true;
            setTimeout(function () {
                eventPending = false;
                try{
                    var data = window.data = realtime.getUserDoc();
                    $sheetJson.text(data);
                    var json = JSON.parse(data);
                    jsonToSheet(ifrw, json);
                }catch(e) { console.log(e.stack); }
            }, 0);
        };

        var key = Crypto.parseKey(window.location.hash.substring(1));
        var myUserName = Crypto.rand64(8);

        var socket = new WebSocket(Config.websocketURL);
        socket.onopen = function () {
            var realtime = ChainPad.create(
                myUserName, 'x', key.channel, '', { transformFunction: Otaml.transform });
            socket.onmessage = function (evt) {
                var message = Crypto.decrypt(evt.data, key.cryptKey);
                realtime.message(message);
            };
            realtime.onMessage(function (message) {
                message = Crypto.encrypt(message, key.cryptKey);
                try {
                    socket.send(message);
                } catch (e) {
                    console.log(e.stack);
                }
            });
            ifrw.sh.on('sheetCellEdited', function () { sheetEvent(realtime); });
            sheetEvent(realtime);
            realtime.onPatch(function () { realtimeEvent(realtime); });

            ifrw.$('.jSTitle').html('');
            Toolbar.create(ifrw.$('.jSTitle'), myUserName, realtime);

            realtime.start();
        };
    });
});