var assert = require('chai').assert;
var ko = require('knockout');

var kf = require('app/client/lib/koForm');
var koArray = require('app/client/lib/koArray');
var clientUtil = require('../clientUtil');

var G = require('app/client/lib/browserGlobals').get('$');

describe('koForm', function() {

  clientUtil.setTmpMochaGlobals();

  function triggerInput(input, property, value) {
    input[property] = value;
    G.$(input).trigger('input');
  }

  function triggerChange(input, property, value) {
    input[property] = value;
    G.$(input).trigger('change');
  }

  function triggerClick(elem) {
    G.$(elem).trigger('click');
  }

  describe("button", function() {
    it("should call a function", function() {
      var calls = 0;
      var btn = kf.button(function() { calls++; }, 'Test');
      triggerClick(btn);
      triggerClick(btn);
      triggerClick(btn);
      assert.equal(calls, 3);
    });
  });

  describe("checkButton", function() {
    it("should bind an observable", function() {
      var obs = ko.observable(false);

      // Test observable->widget binding.
      var btn = kf.checkButton(obs, "Test");
      assert(!btn.classList.contains('active'));
      obs(true);
      assert(btn.classList.contains('active'));

      btn = kf.checkButton(obs, "Test2");
      assert(btn.classList.contains('active'));
      obs(false);
      assert(!btn.classList.contains('active'));

      // Test widget->observable binding.
      assert.equal(obs(), false);
      triggerClick(btn);
      assert.equal(obs(), true);
      triggerClick(btn);
      assert.equal(obs(), false);
    });
  });

  describe("buttonSelect", function() {
    it("should bind an observable", function() {
      var obs = ko.observable('b');
      var a, b, c;

      kf.buttonSelect(obs,
        a = kf.optionButton('a', 'Test A'),
        b = kf.optionButton('b', 'Test B'),
        c = kf.optionButton('c', 'Test C')
      );

      // Test observable->widget binding.
      assert(!a.classList.contains('active'));
      assert(b.classList.contains('active'));
      assert(!c.classList.contains('active'));
      obs('a');
      assert(a.classList.contains('active'));
      assert(!b.classList.contains('active'));
      obs('c');
      assert(!a.classList.contains('active'));
      assert(!b.classList.contains('active'));
      assert(c.classList.contains('active'));

      // Test widget->observable binding.
      assert.equal(obs(), 'c');
      triggerClick(b);
      assert.equal(obs(), 'b');
    });
  });

  describe("checkbox", function() {
    it("should bind an observable", function() {
      var obs = ko.observable(false);
      var check = kf.checkbox(obs, "Foo").querySelector('input');

      // Test observable->widget binding.
      assert.equal(check.checked, false);
      obs(true);
      assert.equal(check.checked, true);

      check = kf.checkbox(obs, "Foo").querySelector('input');
      assert.equal(check.checked, true);
      obs(false);
      assert.equal(check.checked, false);

      // Test widget->observable binding.
      triggerChange(check, 'checked', true);
      assert.equal(obs(), true);
      assert.equal(check.checked, true);

      triggerChange(check, 'checked', false);
      assert.equal(obs(), false);
      assert.equal(check.checked, false);
    });
  });

  describe("text", function() {
    it("should bind an observable", function() {
      var obs = ko.observable('hello');
      var input = kf.text(obs).querySelector('input');

      // Test observable->widget binding.
      assert.equal(input.value, 'hello');
      obs('world');
      assert.equal(input.value, 'world');

      // Test widget->observable binding.
      triggerChange(input, 'value', 'foo');
      assert.equal(obs(), 'foo');
    });
  });

  describe("text debounce", function() {
    it("should bind an observable", function() {
      var obs = ko.observable('hello');
      var input = kf.text(obs, {delay: 300}).querySelector('input');

      // Test observable->widget binding.
      assert.equal(input.value, 'hello');
      obs('world');
      assert.equal(input.value, 'world');

      // Test widget->observable binding using interrupted by 'Enter' or loosing focus debounce.
      triggerInput(input, 'value', 'bar');
      assert.equal(input.value, 'bar');
      // Ensure that observable value wasn't changed immediately
      assert.equal(obs(), 'world');
      // Simulate 'change' event (hitting 'Enter' or loosing focus)
      triggerChange(input, 'value', 'bar');
      // Ensure that observable value was changed on 'change' event
      assert.equal(obs(), 'bar');

      // Test widget->observable binding using debounce.
      triggerInput(input, 'value', 'helloworld');
      input.selectionStart = 3;
      input.selectionEnd = 7;
      assert.equal(input.value.substring(input.selectionStart, input.selectionEnd), 'lowo');
      assert.equal(input.value, 'helloworld');

      // Ensure that observable value wasn't changed immediately, needs to wait 300 ms
      assert.equal(obs(), 'bar');

      // Ensure that after delay value were changed
      return clientUtil.waitForChange(obs, 350)
      .then(() => {
        assert.equal(obs(), 'helloworld');
        assert.equal(input.value, 'helloworld');
        // Ensure that selection is the same and cursor didn't jump to the end
        assert.equal(input.value.substring(input.selectionStart, input.selectionEnd), 'lowo');
      });
    });
  });

  describe("numText", function() {
    it("should bind an observable", function() {
      var obs = ko.observable(1234);
      var input = kf.numText(obs).querySelector('input');

      // Test observable->widget binding.
      assert.equal(input.value, '1234');
      obs('-987.654');
      assert.equal(input.value, '-987.654');

      // Test widget->observable binding.
      triggerInput(input, 'value', '-1.2');
      assert.strictEqual(obs(), -1.2);
    });
  });

  describe("select", function() {
    it("should bind an observable", function() {
      var obs = ko.observable("b");
      var input = kf.select(obs, ["a", "b", "c"]).querySelector('select');
      var options = Array.prototype.slice.call(input.querySelectorAll('option'), 0);
      function selected() {
        return options.map(function(option) { return option.selected; });
      }

      // Test observable->widget binding.
      assert.deepEqual(selected(), [false, true, false]);
      obs("a");
      assert.deepEqual(selected(), [true, false, false]);
      obs("c");
      assert.deepEqual(selected(), [false, false, true]);

      // Test widget->observable binding.
      triggerChange(options[0], 'selected', true);
      assert.deepEqual(selected(), [true, false, false]);
      assert.equal(obs(), "a");

      triggerChange(options[1], 'selected', true);
      assert.deepEqual(selected(), [false, true, false]);
      assert.equal(obs(), "b");
    });

    it("should work with option array of objects", function() {
      var obs = ko.observable();
      var foo = ko.observable('foo');
      var bar = ko.observable('bar');
      var values = koArray([
        { label: foo, value: 'a1' },
        { label: bar, value: 'b1' },
      ]);

      var select = kf.select(obs, values);
      var options = Array.from(select.querySelectorAll('option'));
      assert.deepEqual(options.map(el => el.textContent), ['foo', 'bar']);

      triggerChange(options[0], 'selected', true);
      assert.equal(obs(), 'a1');

      foo('foo2');
      bar('bar2');

      options = Array.from(select.querySelectorAll('option'));
      assert.deepEqual(options.map(el => el.textContent), ['foo2', 'bar2']);

      triggerChange(options[1], 'selected', true);
      assert.equal(obs(), 'b1');
    });

    it("should store actual, non-stringified values", function() {
      let obs = ko.observable();
      let values = [
        { label: 'a', value: 1 },
        { label: 'b', value: '2' },
        { label: 'c', value: true },
        { label: 'd', value: { hello: 'world' } },
        { label: 'e', value: new Date() },
      ];
      let options = Array.from(kf.select(obs, values).querySelectorAll('option'));

      for (let i = 0; i < values.length; i++) {
        triggerChange(options[i], 'selected', true);
        assert.strictEqual(obs(), values[i].value);
      }
    });

    it("should allow multi-select and save sorted values", function() {
      let obs = ko.observable();
      let foo = { foo: 'bar' };
      let values = [{ label: 'a', value: foo }, 'd', { label: 'c', value: 1 }, 'b'];
      let options = Array.from(
        kf.select(obs, values, { multiple: true}).querySelectorAll('option'));

      triggerChange(options[0], 'selected', true);
      triggerChange(options[2], 'selected', true);
      triggerChange(options[3], 'selected', true);

      assert.deepEqual(obs(), [1, foo, 'b']);
    });
  });
});