describe('tableLayout', function () {
  var Cell = require('../src/cell');
  var layoutManager = require('../src/layout-manager');
  var layoutTable = layoutManager.layoutTable;
  var makeTableLayout = layoutManager.makeTableLayout;
  var addRowSpanCells = layoutManager.addRowSpanCells;
  var maxWidth = layoutManager.maxWidth;
  var fillInTable = layoutManager.fillInTable;
  var computeWidths = layoutManager.computeWidths;
  var computeHeights = layoutManager.computeHeights;
  var chai = require('chai');
  var expect = chai.expect;
  var _ = require('lodash');

  it('simple 2x2 layout',function(){
    var actual = makeTableLayout([
      ['hello','goodbye'],
      ['hola','adios']
    ]);

    var expected = [
      ['hello','goodbye'],
      ['hola','adios']
    ];

    checkLayout(actual,expected);
  });

  it('cross table',function(){
    var actual = makeTableLayout([
      {'1.0':['yes','no']},
      {'2.0':['hello','goodbye']}
    ]);

    var expected = [
      ['1.0','yes','no'],
      ['2.0','hello','goodbye']
    ];

    checkLayout(actual,expected);
  });

  it('vertical table',function(){
    var actual = makeTableLayout([
      {'1.0':'yes'},
      {'2.0':'hello'}
    ]);

    var expected = [
      ['1.0','yes'],
      ['2.0','hello']
    ];

    checkLayout(actual,expected);
  });

  it('colSpan adds RowSpanCells to the right',function(){
    var actual = makeTableLayout([
      [{content:'hello',colSpan:2}],
      ['hola','adios']
    ]);

    var expected = [
      [{content:'hello',colSpan:2},null],
      ['hola','adios']
    ];

    checkLayout(actual,expected);
  });

  it('rowSpan adds RowSpanCell below',function(){
     var actual = makeTableLayout([
       [{content:'hello',rowSpan:2},'goodbye'],
       ['adios']
     ]);

    var expected = [
      ['hello'            , 'goodbye'],
      [{spannerFor:[0,0]} , 'adios']
    ];

    checkLayout(actual,expected);
  });

  it('rowSpan and cellSpan together',function(){
    var actual = makeTableLayout([
      [{content:'hello',rowSpan:2,colSpan:2},'goodbye'],
      ['adios']
    ]);

    var expected = [
      ['hello'            , null, 'goodbye'],
      [{spannerFor:[0,0]} , null, 'adios']
    ];

    checkLayout(actual,expected);
  });

  it('complex layout',function(){
    var actual = makeTableLayout([
      [{content:'hello',rowSpan:2,colSpan:2},{content:'yo',rowSpan:2,colSpan:2},'goodbye'],
      ['adios']
    ]);

    var expected = [
      ['hello'            , null, 'yo'               , null, 'goodbye'],
      [{spannerFor:[0,0]} , null, {spannerFor:[0,2]} , null, 'adios']
    ];

    checkLayout(actual,expected);
  });

  it('complex layout2',function(){
    var actual = makeTableLayout([
      ['a','b',                          {content:'c',rowSpan:3,colSpan:2},'d'],
      [{content:'e',rowSpan:2,colSpan:2}, 'f'],
      ['g']
    ]);

    var expected = [
      ['a',                 'b',   'c',                 null,  'd'],
      ['e',                 null,  {spannerFor:[0,2]},  null,  'f'],
      [{spannerFor:[1,0]},  null,  {spannerFor:[0,2]},  null,  'g']
    ];

    checkLayout(actual,expected);
  });

  it('stairstep spans',function(){
    var actual = makeTableLayout([
      [{content:'',rowSpan:2},''],
      [{content:'',rowSpan:2}],
      ['']
    ]);

    var expected = [
      [{content:'',rowSpan:2}, ''],
      [{spannerFor:[0,0]},{content:'',rowSpan:2}],
      ['',{spannerFor:[1,1]}]
    ];

    checkLayout(actual,expected);
  });

  describe('fillInTable',function(){
    function mc(opts,y,x){
      var cell = new Cell(opts);
      cell.x = x;
      cell.y = y;
      return cell;
    }

    it('will blank out individual cells',function(){
      var cells = [
        [mc('a',0,1)],
        [mc('b',1,0)]
      ];
      fillInTable(cells);

      checkLayout(cells,[
        ['', 'a'],
        ['b', '']
      ]);
    });

    it('will autospan to the right',function(){
      var cells = [
        [],
        [mc('a',1,1)]
      ];
      fillInTable(cells);

      checkLayout(cells,[
        [{content:'',colSpan:2}, null],
        ['', 'a']
      ]);
    });

    it('will autospan down',function(){
      var cells = [
        [ mc('a',0,1)],
        []
      ];
      fillInTable(cells);
      addRowSpanCells(cells);

      checkLayout(cells,[
        [{content:'',rowSpan:2}, 'a'],
        [{spannerFor:[0,0]}, '']
      ]);
    });

    it('will autospan right and down',function(){
      var cells = [
        [               mc('a',0,2)],
        [],
        [  mc('b',2,1)]
      ];
      fillInTable(cells);
      addRowSpanCells(cells);

      checkLayout(cells,[
        [{content:'',colSpan:2, rowSpan:2}, null, 'a'],
        [{spannerFor:[0,0]}, null, {content:'', colSpan:1, rowSpan:2}],
        ['','b',{spannerFor:[1,2]}]
      ]);
    });
  });

  describe('computeWidths',function() {
    function mc(y,x,desiredWidth, colSpan) {
      return {x:x,y:y,desiredWidth:desiredWidth,colSpan:colSpan};
    }

    it('finds the maximum desired width of each column', function () {
      var widths = [];
      var cells = [
        [mc(0,0,7), mc(0,1,3), mc(0,2,5)],
        [mc(1,0,8), mc(1,1,5), mc(1,2,2)],
        [mc(2,0,6), mc(2,1,9), mc(2,2,1)]
      ];

      computeWidths(widths, cells);

      expect(widths).to.eql([8, 9, 5]);
    });

    it('won\'t touch hard coded values', function () {
      var widths = [null, 3];
      var cells = [
        [mc(0,0,7), mc(0,1,3), mc(0,2,5)],
        [mc(1,0,8), mc(1,1,5), mc(1,2,2)],
        [mc(2,0,6), mc(2,1,9), mc(2,2,1)]
      ];

      computeWidths(widths, cells);

      expect(widths).to.eql([8, 3, 5]);
    });

    it('assumes undefined desiredWidth is 1', function () {
      var widths = [];
      var cells = [[{x:0,y:0}], [{x:0,y:1}], [{x:0,y:2}]];
      computeWidths(widths, cells);
      expect(widths).to.eql([1])
    });

    it('takes into account colSpan and wont over expand', function () {
      var widths = [];
      var cells = [
        [mc(0,0,10, 2),        mc(0,2,5)],
        [mc(1,0,5), mc(1,1,5), mc(1,2,2)],
        [mc(2,0,4), mc(2,1,2), mc(2,2,1)]
      ];
      computeWidths(widths, cells);
      expect(widths).to.eql([5, 5, 5]);
    });

    it('will expand rows involved in colSpan in a balanced way', function () {
      var widths = [];
      var cells = [
        [mc(0,0,13,2),          mc(0,2,5)],
        [mc(1,0,5), mc(1,1,5),  mc(1,2,2)],
        [mc(2,0,4), mc(2,1,2),  mc(2,2,1)]
      ];
      computeWidths(widths, cells);
      expect(widths).to.eql([6, 6, 5]);
    });

    it('expands across 3 cols', function () {
      var widths = [];
      var cells = [
        [mc(0,0,25,3)                        ],
        [mc(1,0,5),    mc(1,1,5),  mc(1,2,2) ],
        [mc(2,0,4),    mc(2,1,2),  mc(2,2,1) ]
      ];
      computeWidths(widths, cells);
      expect(widths).to.eql([9, 9, 5]);
    });

    it('multiple spans in same table', function () {
      var widths = [];
      var cells = [
        [mc(0,0,25,3)                        ],
        [mc(1,0,30,3)                        ],
        [mc(2,0,4),    mc(2,1,2),  mc(2,2,1) ]
      ];
      computeWidths(widths, cells);
      expect(widths).to.eql([11, 9, 8]);
    });

    it('spans will only edit uneditable tables',function(){
      var widths = [null, 3];
      var cells = [
        [mc(0,0,20,3)                         ],
        [mc(1,0,4),    mc(1,1,20),  mc(1,2,5) ]
      ];
      computeWidths(widths, cells);
      expect(widths).to.eql([7,3,8])
    });

    it('spans will only edit uneditable tables - first column uneditable',function(){
      var widths = [3];
      var cells = [
        [mc(0,0,20,3)                        ],
        [mc(1,0,4),    mc(1,1,3),  mc(1,2,5) ]
      ];
      computeWidths(widths, cells);
      expect(widths).to.eql([3,7,8])
    });
  });

  describe('computeHeights',function(){
    function mc(y,x,desiredHeight,colSpan){
      return {x:x,y:y,desiredHeight:desiredHeight,rowSpan:colSpan};
    }

    it('finds the maximum desired height of each row',function(){
      var heights = [];
      var cells = [
        [mc(0,0,7),  mc(0,1,3),  mc(0,2,5) ],
        [mc(1,0,8),  mc(1,1,5),  mc(1,2,2) ],
        [mc(2,0,6),  mc(2,1,9),  mc(2,2,1) ]
      ];

      computeHeights(heights,cells);

      expect(heights).to.eql([7,8,9]);
    });

    it('won\'t touch hard coded values',function(){
      var heights = [null,3];
      var cells = [
        [mc(0,0,7),  mc(0,1,3),  mc(0,2,5)],
        [mc(1,0,8),  mc(1,1,5),  mc(1,2,2)],
        [mc(2,0,6),  mc(2,1,9),  mc(2,2,1)]
      ];

      computeHeights(heights,cells);

      expect(heights).to.eql([7,3,9]);
    });

    it('assumes undefined desiredHeight is 1',function(){
      var heights = [];
      var cells = [[{x:0,y:0},{x:1,y:0},{x:2,y:0}]];
      computeHeights(heights,cells);
      expect(heights).to.eql([1])
    });

    it('takes into account rowSpan and wont over expand',function(){
      var heights = [];
      var cells = [
        [mc(0,0,10,2),  mc(0,1,5),  mc(0,2,2)],
        [               mc(1,1,5),  mc(1,2,2)],
        [mc(2,0,4),     mc(2,1,2),  mc(2,2,1)]
      ];
      computeHeights(heights,cells);
      expect(heights).to.eql([5,5,4]);
    });

    it('will expand rows involved in rowSpan in a balanced way',function(){
      var heights = [];
      var cells = [
        [mc(0,0,13,2), mc(0,1,5), mc(0,2,5)],
        [              mc(1,1,5), mc(1,2,2)],
        [mc(2,0,4),    mc(2,1,2), mc(2,2,1)]
      ];
      computeHeights(heights,cells);
      expect(heights).to.eql([6,6,4]);
    });

    it('expands across 3 rows',function(){
      var heights = [];
      var cells = [
        [mc(0,0,25,3), mc(0,1,5), mc(0,2,4)],
        [              mc(1,1,5), mc(1,2,2)],
        [              mc(2,1,2), mc(2,2,1)]
      ];
      computeHeights(heights,cells);
      expect(heights).to.eql([9,9,5]);
    });

    it('multiple spans in same table',function(){
      var heights = [];
      var cells = [
        [mc(0,0,25,3), mc(0,1,30,3), mc(0,2,4)],
        [                            mc(1,2,2)],
        [                            mc(2,2,1)]
      ];
      computeHeights(heights,cells);
      expect(heights).to.eql([11,9,8]);
    });
  });

  /**
   * Provides a shorthand for validating a table of cells.
   * To pass, both arrays must have the same dimensions, and each cell in `actualRows` must
   * satisfy the shorthand assertion of the corresponding location in `expectedRows`.
   *
   * Available Expectations Can Be:
   *
   *    * A `String` -  Must be a normal cell with contents equal to the String value.
   *    * `null` -  Must be a RowSpanCell
   *
   * Or an `Object` with any of the following properties (multiple properties allowed):
   *    * rowSpan:Number - Must be a normal cell with the given rowSpan.
   *    * colSpan:Number - Must be a normal cell with the given colSpan.
   *    * content:String - Must be a normal cell with the given content.
   *    * spannerFor:[row,col] - Must be a RowSpanCell delegating to the cell at the given coordinates.
   *
   * @param actualRows - the table of cells under test.
   * @param expectedRows - a table of shorthand assertions.
   */

  function checkLayout(actualTable,expectedTable){
    _.forEach(expectedTable,function(expectedRow,y){
      _.forEach(expectedRow,function(expectedCell,x){
        if(expectedCell !== null){
          var actualCell = findCell(actualTable,x,y);
          checkExpectation(actualCell,expectedCell,x,y,actualTable);
        }
      });
    });
  }

  function findCell(table,x,y){
    for(var i = 0; i < table.length; i++){
      var row = table[i];
      for(var j = 0; j < row.length; j++){
        var cell = row[j];
        if(cell.x === x && cell.y === y){
          return cell;
        }
      }
    }
  }

  function checkExpectation(actualCell,expectedCell,x,y,actualTable){
    if(_.isString(expectedCell)){
      expectedCell = {content:expectedCell};
    }
    var address = '(' + y + ',' + x + ')';
    if(expectedCell.hasOwnProperty('content')){
      expect(actualCell, address).to.be.instanceOf(Cell);
      expect(actualCell.content,'content of ' + address).to.equal(expectedCell.content);
    }
    if(expectedCell.hasOwnProperty('rowSpan')){
      expect(actualCell, address).to.be.instanceOf(Cell);
      expect(actualCell.rowSpan, 'rowSpan of ' + address).to.equal(expectedCell.rowSpan);
    }
    if(expectedCell.hasOwnProperty('colSpan')){
      expect(actualCell, address).to.be.instanceOf(Cell);
      expect(actualCell.colSpan, 'colSpan of ' + address).to.equal(expectedCell.colSpan);
    }
    if(expectedCell.hasOwnProperty('spannerFor')){
      expect(actualCell, address).to.be.instanceOf(Cell.RowSpanCell);
      expect(actualCell.originalCell,address + 'originalCell should be a cell').to.be.instanceOf(Cell);
      expect(actualCell.originalCell,address + 'originalCell not right').to.equal(findCell(actualTable,
        expectedCell.spannerFor[1],
        expectedCell.spannerFor[0]
      ));
      //TODO: retest here x,y coords
    }
  }
});