describe('Chartist core', function() {
  'use strict';

  beforeEach(function() {

  });

  afterEach(function() {

  });

  describe('createSvg tests', function () {
    it('should not remove non-chartist svg elements', function() {
      jasmine.getFixtures().set('<div id="chart-container"><svg id="foo"></svg><div><svg id="bar"></svg></div></div>');

      var container = $('#chart-container'),
        // We use get(0) because we want the DOMElement, not the jQuery object.
        svg = Chartist.createSvg(container.get(0), '500px', '400px', 'ct-fish-bar');

      expect(svg).toBeDefined();
      expect(svg.classes()).toContain('ct-fish-bar');
      expect(container).toContainElement('#foo');
      expect(container).toContainElement('#bar');
    });

    it('should remove previous chartist svg elements', function() {
      jasmine.getFixtures().set('<div id="chart-container"></div>');

      var container = $('#chart-container'),
        // We use get(0) because we want the DOMElement, not the jQuery object.
        svg1 = Chartist.createSvg(container.get(0), '500px', '400px', 'ct-fish-bar'),
        svg2 = Chartist.createSvg(container.get(0), '800px', '200px', 'ct-snake-bar');

      expect(svg1).toBeDefined();
      expect(svg1.classes()).toContain('ct-fish-bar');
      expect(svg2).toBeDefined();
      expect(svg2.classes()).toContain('ct-snake-bar');
      expect(container).not.toContainElement('.ct-fish-bar');
      expect(container).toContainElement('.ct-snake-bar');
    });
  });

  describe('serialization tests', function () {
    it('should serialize and deserialize regular strings', function() {
      var input = 'String test';
      expect(input).toMatch(Chartist.deserialize(Chartist.serialize(input)));
    });

    it('should serialize and deserialize strings with critical characters', function() {
      var input = 'String test with critical characters " < > \' & &amp;';
      expect(input).toMatch(Chartist.deserialize(Chartist.serialize(input)));
    });

    it('should serialize and deserialize numbers', function() {
      var input = 12345.6789;
      expect(input).toEqual(Chartist.deserialize(Chartist.serialize(input)));
    });

    it('should serialize and deserialize dates', function() {
      var input = new Date(0);
      expect(+input).toEqual(+new Date(Chartist.deserialize(Chartist.serialize(input))));
    });

    it('should serialize and deserialize complex object types', function() {
      var input = {
        a: {
          b: 100,
          c: 'String test',
          d: 'String test with critical characters " < > \' & &amp;',
          e: {
            f: 'String test'
          }
        }
      };

      expect(input).toEqual(Chartist.deserialize(Chartist.serialize(input)));
    });

    it('should serialize and deserialize null, undefined and NaN', function() {
      expect(null).toEqual(Chartist.deserialize(Chartist.serialize(null)));
      expect(undefined).toEqual(Chartist.deserialize(Chartist.serialize(undefined)));
      expect(NaN).toMatch(Chartist.deserialize(Chartist.serialize('NaN')));
    });
  });

  describe('data normalization tests', function () {

    it('normalize mixed series types correctly', function() {
      var data = {
        labels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
        series: [
          {data: [1, 0, 3, 4, 5, 6]},
          [1, {value: 0}, 3, {value: 4}, 5, 6, 7, 8],
          {data: [1, 0, {value: 3}]}
        ]
      };

      expect(Chartist.getDataArray(data)).toEqual(
        [
          [1, 0, 3, 4, 5, 6],
          [1, 0, 3, 4, 5, 6, 7, 8],
          [1, 0, 3]
        ]
      );
    });

    it('normalize mixed series for pie chart correctly', function() {
      var data = {
        series: [1, {value: 0}, 3, {value: 4}, 5, 6, 7, 8]
      };

      expect(Chartist.getDataArray(data)).toEqual(
        [1, 0, 3, 4, 5, 6, 7, 8]
      );
    });

    it('normalize mixed series with string values for pie chart correctly', function() {
      var data = {
        series: ['1', {value: '0'}, '3', {value: '4'}, '5', '6', '7', '8']
      };

      expect(Chartist.getDataArray(data)).toEqual(
        [1, 0, 3, 4, 5, 6, 7, 8]
      );
    });

    it('normalize mixed series types with string values correctly', function() {
      var data = {
        labels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
        series: [
          {data: ['1', '0', '3', '4', '5', '6']},
          ['1', {value: '0'}, '3', {value: '4'}, '5', '6', '7', '8'],
          {data: ['1', '0', {value: '3'}]}
        ]
      };

      expect(Chartist.getDataArray(data)).toEqual(
        [
          [1, 0, 3, 4, 5, 6],
          [1, 0, 3, 4, 5, 6, 7, 8],
          [1, 0, 3]
        ]
      );
    });

    it('normalize mixed series types with weird values correctly', function() {
      var data = {
        labels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
        series: [
          {data: [null, NaN, undefined, '4', '5', '6']},
          ['1', {value: null}, '3', {value: NaN}, '5', '6', '7', '8'],
          {data: ['1', '0', {value: undefined}]}
        ]
      };

      expect(Chartist.getDataArray(data)).toEqual(
        [
          [undefined, undefined, undefined, 4, 5, 6],
          [1, undefined, 3, undefined, 5, 6, 7, 8],
          [1, 0, undefined]
        ]
      );
    });

    it('should normalize correctly with 0 values in data series array objects', function() {
      var data = {
        labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
        series: [{
          data: [
            { value: 1 },
            { value: 4 },
            { value: 2 },
            { value: 7 },
            { value: 2 },
            { value: 0 }
          ]
        }]
      };

      expect(Chartist.getDataArray(data)).toEqual(
        [[1, 4, 2, 7, 2, 0]]
      );
    });

    it('should normalize correctly with mixed dimensional input into multi dimensional output', function() {
      var data = {
        labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
        series: [{
          data: [
            { value: 1 },
            { value: {y: 4, x: 1}},
            { y: 2, x: 2},
            NaN,
            null,
            { value: 7 },
            { value: 2 },
            { value: null },
            { y: undefined, x: NaN }
          ]
        }]
      };

      expect(Chartist.getDataArray(data, false, true)).toEqual(
        [[
          {x: undefined, y: 1},
          {x: 1, y: 4},
          {x: 2, y: 2},
          undefined,
          undefined,
          {x: undefined, y: 7},
          {x: undefined, y: 2},
          undefined,
          {x: undefined, y: undefined}
        ]]
      );
    });

    it('should normalize boolean series correctly', function() {
      var data = {
        series: [true, false, false, true]
      };

      expect(Chartist.getDataArray(data)).toEqual(
        [1, 0, 0, 1]
      );
    });

    it('should normalize date series correctly', function() {
      var data = {
        series: [new Date(0), new Date(1), new Date(2), new Date(3)]
      };

      expect(Chartist.getDataArray(data)).toEqual(
        [0, 1, 2, 3]
      );
    });
  });

  describe('padding normalization tests', function () {
    it('should normalize number padding', function() {
      expect(Chartist.normalizePadding(10)).toEqual({
        top: 10,
        right: 10,
        bottom: 10,
        left: 10
      });
    });

    it('should normalize number padding when 0 is passed', function() {
      expect(Chartist.normalizePadding(0)).toEqual({
        top: 0,
        right: 0,
        bottom: 0,
        left: 0
      });
    });

    it('should normalize empty padding object with default fallback', function() {
      expect(Chartist.normalizePadding({})).toEqual({
        top: 0,
        right: 0,
        bottom: 0,
        left: 0
      });
    });

    it('should normalize empty padding object with specified fallback', function() {
      expect(Chartist.normalizePadding({}, 10)).toEqual({
        top: 10,
        right: 10,
        bottom: 10,
        left: 10
      });
    });

    it('should normalize partial padding object with specified fallback', function() {
      expect(Chartist.normalizePadding({
        top: 5,
        left: 5
      }, 10)).toEqual({
        top: 5,
        right: 10,
        bottom: 10,
        left: 5
      });
    });

    it('should not modify complete padding object', function() {
      expect(Chartist.normalizePadding({
        top: 5,
        right: 5,
        bottom: 5,
        left: 5
      }, 10)).toEqual({
        top: 5,
        right: 5,
        bottom: 5,
        left: 5
      });
    });
  });

  describe('quantity', function() {

    it('should return value for numbers', function() {
      expect(Chartist.quantity(100)).toEqual({ value: 100 });
      expect(Chartist.quantity(0)).toEqual({ value: 0 });
      expect(Chartist.quantity(NaN)).toEqual({ value: NaN });
      expect(Chartist.quantity(null)).toEqual({ value: null });
      expect(Chartist.quantity(undefined)).toEqual({ value: undefined });
    });

    it('should return value without unit from string', function() {
      expect(Chartist.quantity('100')).toEqual({ value: 100, unit : undefined });
      expect(Chartist.quantity('0')).toEqual({ value: 0, unit : undefined });
    });

    it('should return value and unit from string', function() {
      expect(Chartist.quantity('100%')).toEqual({ value: 100, unit :'%' });
      expect(Chartist.quantity('100 %')).toEqual({ value: 100, unit :'%' });
      expect(Chartist.quantity('0px')).toEqual({ value: 0, unit: 'px' });
    });

  });

  describe('getBounds', function() {

    it('should return 10 steps', function() {
      var bounds = Chartist.getBounds(100, { high: 10, low: 1 }, 10, false);
      expect(bounds.min).toBe(1);
      expect(bounds.max).toBe(10);
      expect(bounds.values).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
    });

    it('should return 5 steps', function() {
      var bounds = Chartist.getBounds(100, { high: 10, low: 1 }, 20, false);
      expect(bounds.min).toBe(1);
      expect(bounds.max).toBe(10);
      expect(bounds.values).toEqual([1, 3, 5, 7, 9]);
      // Is this correct behaviour? Should it include 10?
    });

    it('should return non integer steps', function() {
      var bounds = Chartist.getBounds(100, { high: 2, low: 1 }, 20, false);
      expect(bounds.min).toBe(1);
      expect(bounds.max).toBe(2);
      expect(bounds.values).toEqual([ 1, 1.25, 1.5, 1.75, 2 ]);
    });

    it('should return integer steps only', function() {
      var bounds = Chartist.getBounds(100, { high: 3, low: 1 }, 20, true);
      expect(bounds.min).toBe(1);
      expect(bounds.max).toBe(3);
      expect(bounds.values).toEqual([ 1, 2, 3 ]);
    });

    it('should return single integer step', function() {
      var bounds = Chartist.getBounds(100, { high: 2, low: 1 }, 20, true);
      expect(bounds.min).toBe(1);
      expect(bounds.max).toBe(2);
      expect(bounds.values).toEqual([ 1, 2,]);
    });

    it('should floor/ceil min/max', function() {
      var bounds = Chartist.getBounds(100, { high: 9.9, low: 1.01 }, 20, false);
      expect(bounds.min).toBe(1);
      expect(bounds.max).toBe(10);
      expect(bounds.values).toEqual([1, 3, 5, 7, 9]);
      // Is this correct behaviour? Should it include 10?
    });

    it('should floor/ceil min/max for non integers', function() {
      var bounds = Chartist.getBounds(100, { high: 2.9, low: 1.01 }, 20, false);
      expect(bounds.min).toBe(1);
      expect(bounds.max).toBe(3);
      expect(bounds.values).toEqual([1, 1.5, 2, 2.5, 3]);
    });

    it('should floor/ceil min/max if integers only', function() {
      var bounds = Chartist.getBounds(100, { high: 2.9, low: 1.01 }, 20, true);
      expect(bounds.min).toBe(1);
      expect(bounds.max).toBe(3);
      expect(bounds.values).toEqual([1, 2, 3]);
    });

    it('should return neg and pos values', function() {
      var bounds = Chartist.getBounds(100, { high: 1.9, low: -0.9 }, 20, false);
      expect(bounds.min).toBe(-1);
      expect(bounds.max).toBe(2);
      expect(bounds.values).toEqual([-1, 0, 1, 2]);
    });

    it('should return two steps if no space', function() {
      var bounds = Chartist.getBounds(100, { high: 5, low: 0 }, 45, false);
      expect(bounds.min).toBe(0);
      expect(bounds.max).toBe(5);
      expect(bounds.values).toEqual([0, 4]);
      // Is this correct behaviour? Should it be [0, 5]?
    });

    it('should return single step if no space', function() {
      var bounds = Chartist.getBounds(100, { high: 5, low: 0 }, 80, false);
      expect(bounds.min).toBe(0);
      expect(bounds.max).toBe(5);
      expect(bounds.values).toEqual([0]);
      // Is this correct behaviour? Should it be [0, 5]?
    });

    it('should return single step if range is less than epsilon', function() {
      var bounds = Chartist.getBounds(100, { high: 1.0000000000000002, low: 1 }, 20, false);
      expect(bounds.min).toBe(1);
      expect(bounds.max).toBe(1.0000000000000002);
      expect(bounds.low).toBe(1);
      expect(bounds.high).toBe(1.0000000000000002);
      expect(bounds.values).toEqual([1]);
    });

    it('should return single step if range is less than smallest increment', function() {
      var bounds = Chartist.getBounds(613.234375, { high: 1000.0000000000001, low: 999.9999999999997 }, 50, false);
      expect(bounds.min).toBe(999.9999999999999);
      expect(bounds.max).toBe(1000);
      expect(bounds.low).toBe(999.9999999999997);
      expect(bounds.high).toBe(1000.0000000000001);
      expect(bounds.values).toEqual([Chartist.roundWithPrecision(999.9999999999999)]);
    });

  });

  describe('splitIntoSegments', function() {

    function makeValues(arr) {
      return arr.map(function(x) {
        return { value: x };
      });
    }

    it('should return empty array for empty input', function() {
      expect(Chartist.splitIntoSegments([],[])).toEqual([]);
    });

    it('should remove undefined values', function() {
      var coords = [1,2,3,4,5,6,7,8,9,10,11,12];
      var values = makeValues([1,undefined,undefined,4,undefined,6]);

      expect(Chartist.splitIntoSegments(coords, values)).toEqual([{
        pathCoordinates: [1,2],
        valueData: makeValues([1])
      }, {
        pathCoordinates: [7, 8],
        valueData: makeValues([4])
      }, {
        pathCoordinates: [11, 12],
        valueData: makeValues([6])
      }]);
    });

    it('should respect fillHoles option', function() {
      var coords = [1,2,3,4,5,6,7,8,9,10,11,12];
      var values = makeValues([1,undefined,undefined,4,undefined,6]);
      var options = {
        fillHoles: true
      };

      expect(Chartist.splitIntoSegments(coords, values, options)).toEqual([{
        pathCoordinates: [1,2,7,8,11,12],
        valueData: makeValues([1,4,6])
      }]);
    });

    it('should respect increasingX option', function() {
      var coords = [1,2,3,4,5,6,5,6,7,8,1,2];
      var values = makeValues([1,2,3,4,5,6]);
      var options = {
        increasingX: true
      };

      expect(Chartist.splitIntoSegments(coords, values, options)).toEqual([{
        pathCoordinates: [1,2,3,4,5,6],
        valueData: makeValues([1,2,3])
      }, {
        pathCoordinates: [5,6,7,8],
        valueData: makeValues([4,5])
      }, {
        pathCoordinates: [1,2],
        valueData: makeValues([6])
      }]);
    });
  });

  describe('createGrid', function() {
    var group, axis, classes, eventEmitter, position, length, offset;

    beforeEach(function() {
      eventEmitter = Chartist.EventEmitter();
      group = new Chartist.Svg('g');
      axis = {
        units: {
          pos : 'x'
        },
        counterUnits: {
          pos : 'y'
        }
      };
      classes = [];
      position = 10;
      length = 100;
      offset = 20;
    });

    function onCreated(fn, done) {
      eventEmitter.addEventHandler('draw', function(grid) {
        fn(grid);
        done();
      });
      Chartist.createGrid(position, 1, axis, offset, length, group, classes, eventEmitter);
    }

    it('should add single grid line to group', function(done) {
      onCreated(function() {
        expect(group.querySelectorAll('line').svgElements.length).toBe(1);
      }, done);
    });

    it('should draw line', function(done) {
      onCreated(function() {
        var line = group.querySelector('line');
        expect(line.attr('x1')).toBe('10');
        expect(line.attr('x2')).toBe('10');
        expect(line.attr('y1')).toBe('20');
        expect(line.attr('y2')).toBe('120');
      }, done);
    });

    it('should draw horizontal line', function(done) {
      axis.units.pos = 'y';
      axis.counterUnits.pos = 'x';
      onCreated(function() {
        var line = group.querySelector('line');
        expect(line.attr('y1')).toBe('10');
        expect(line.attr('y2')).toBe('10');
        expect(line.attr('x1')).toBe('20');
        expect(line.attr('x2')).toBe('120');
      }, done);
    });

  });

  describe('createGridBackground', function() {
    var group, chartRect, className, eventEmitter;

    beforeEach(function() {
      eventEmitter = Chartist.EventEmitter();
      group = new Chartist.Svg('g');
      className = 'ct-test';
      chartRect = {
        x1 : 5,
        y2 : 10,
        _width : 100,
        _height : 50,
        width : function() { return this._width; },
        height : function() { return this._height; },
      };
    });

    function onCreated(fn, done) {
      eventEmitter.addEventHandler('draw', function(data) {
        fn(data);
        done();
      });
      Chartist.createGridBackground(group, chartRect, className, eventEmitter);
    }

    it('should add rect', function(done) {
      onCreated(function() {
        var rects = group.querySelectorAll('rect').svgElements;
        expect(rects.length).toBe(1);
        var rect = rects[0];
        expect(rect.attr('x')).toBe('5');
        expect(rect.attr('y')).toBe('10');
        expect(rect.attr('width')).toBe('100');
        expect(rect.attr('height')).toBe('50');
        expect(rect.classes()).toEqual(['ct-test']);
      }, done);
    });

    it('should pass grid to event', function(done) {
      onCreated(function(data) {
        expect(data.type).toBe('gridBackground');
        var rect = data.element;
        expect(rect.attr('x')).toBe('5');
        expect(rect.attr('y')).toBe('10');
      }, done);
    });


  });
});
