import 'mocha';
import { assert } from 'chai';
import { LayrCompiler } from '../src/compiler/layr.js';
import { LdmlCompilerMessages } from '../src/compiler/ldml-compiler-messages.js';
import { compilerTestCallbacks, testCompilationCases } from './helpers/index.js';
import { KMXPlus } from '@keymanapp/common-types';
import { constants } from '@keymanapp/ldml-keyboard-constants';

import Layr = KMXPlus.Layr;
import LayrRow = KMXPlus.LayrRow;
import { withOffset } from '@keymanapp/developer-utils';

function allKeysOk(row : LayrRow, str : string, msg? : string) {
  const split = str.split(' ');
  assert.equal(row.keys.length, split.length, msg);
  for (let i=0; i<row.keys.length; i++) {
    assert.equal(row.keys[i].value, split[i], `${msg||'keys row: '}@#${i}`);
  }
}

describe('layr', function () {
  this.slow(500); // 0.5 sec -- json schema validation takes a while

  testCompilationCases(LayrCompiler, [
    {
      subpath: 'sections/keys/minimal.xml',
      callback(sect) {
        const layr = <Layr> sect;
        assert.ok(layr);
        assert.equal(compilerTestCallbacks.messages.length, 0);

        assert.equal(layr.lists?.length, 1);
        const list0 = layr.lists[0];
        assert.ok(list0);
        assert.equal(list0.layers.length, 1);
        assert.equal(list0.hardware.value, 'us');
        const layer0 = list0.layers[0];
        assert.ok(layer0);
        assert.equal(layer0.rows.length, 1);
        const row0 = layer0.rows[0];
        assert.ok(row0);
        assert.equal(row0.keys.length, 2);

        assert.equal(layer0.id.value, 'base');
        assert.equal(layer0.mod, constants.keys_mod_none);
        assert.equal(row0.keys[0]?.value, 'grave');
      },
    },
    {
      subpath: 'sections/keys/maximal.xml',
      callback(sect) {
        const layr = <Layr> sect;
        assert.equal(layr.lists?.length, 2);

        const listHardware = layr.lists.find(v => v.hardware.value === 'iso');
        assert.ok(listHardware);
        assert.equal(listHardware.minDeviceWidth, 0);
        assert.equal(listHardware.layers.length, 2);
        const hardware0 = listHardware.layers[0];
        assert.ok(hardware0);
        assert.equal(hardware0.id.value, 'base');
        assert.equal(hardware0.mod, constants.keys_mod_none);
        const hardware0row0 = hardware0.rows[0];
        assert.ok(hardware0row0);
        assert.equal(hardware0row0.keys.length, 2);
        allKeysOk(hardware0row0,'Q W', 'hardware0row0');
        const hardware1 = listHardware.layers[1];
        assert.ok(hardware1);
        assert.equal(hardware1.rows.length, 1);
        assert.equal(hardware1.id.value, 'shift');
        assert.equal(hardware1.mod, constants.keys_mod_shift);
        const hardware1row0 = hardware1.rows[0];
        assert.ok(hardware1row0);
        assert.equal(hardware1row0.keys.length, 3);
        allKeysOk(hardware1row0,'q w amarker', 'hardware1row0');

        const listTouch = layr.lists.find(v => v.hardware.value === constants.layr_list_hardware_touch);
        assert.ok(listTouch);
        assert.equal(listTouch.minDeviceWidth, 300);
        assert.equal(listTouch.layers.length, 1);
        const touch0 = listTouch.layers[0];
        assert.ok(touch0);
        assert.equal(touch0.rows.length, 1);
        assert.equal(touch0.id.value, 'base');
        assert.equal(touch0.mod, constants.keys_mod_none);
        const touch0row0 = touch0.rows[0];
        assert.ok(touch0row0);
        assert.equal(touch0row0.keys.length, 4);
        allKeysOk(touch0row0,'Q q W w', 'touch0row0');
      },
    },
    {
      subpath: 'sections/layr/invalid-missing-hardware.xml',
      errors: [],
    },
    {
      subpath: 'sections/keys/invalid-bad-modifier.xml',
      errors: [
        LdmlCompilerMessages.Error_InvalidModifier({
          modifiers: 'altR-shift',
        }),
      ],
    },
    {
      subpath: 'sections/layr/invalid-multi-hardware.xml',
      errors: [LdmlCompilerMessages.Error_ExcessHardware({ formId: 'iso' })],
    },
    {
      // missing layer element
      subpath: 'sections/layr/invalid-missing-layer.xml',
      errors: [LdmlCompilerMessages.Error_MustBeAtLeastOneLayerElement(withOffset(40))],
      retainOffsetInMessages: true,
    },
    {
      // missing layer element
      subpath: 'sections/layr/invalid-missing-layer2.xml',
      errors: [LdmlCompilerMessages.Error_MustBeAtLeastOneLayerElement(withOffset(40))],
      retainOffsetInMessages: true,
    },
    {
      // keep in sync with similar test in test-keys.ts
      subpath: 'sections/keys/many-modifiers.xml',
      callback(sect) {
        const layr = <Layr> sect;
        assert.ok(layr);
        assert.equal(layr.lists.length, 1, 'layr.lists.length');
        const layers = layr.lists[0];
        const bymod = layers.layers.map(({id,mod,rows})=>([
          id.value, mod, rows[0].keys[0].value,
        ]));
        assert.sameDeepMembers(bymod, [
          // flatten the layers for comparison, assume a single key
          ['base', constants.keys_mod_none, 'a'],
          ['', constants.keys_mod_altR, 'c'],
          ['', constants.keys_mod_ctrl | constants.keys_mod_shift, 'c'],
        ]);
      },
    },
    {
      subpath: 'sections/layr/error-bogus-modifiers.xml',
      errors: [
        LdmlCompilerMessages.Error_InvalidModifier({ modifiers: 'caps bogus' }),
      ]
    },
    {
      subpath: 'sections/layr/row-keys-whitespace.xml',
      callback(sect) {
        const layr = <Layr> sect;
        assert.ok(layr);
        assert.equal(compilerTestCallbacks.messages.length, 0);

        assert.equal(layr.lists?.length, 1);
        const list0 = layr.lists[0];
        assert.ok(list0);
        assert.equal(list0.layers.length, 1);
        assert.equal(list0.hardware.value, 'us');
        const layer0 = list0.layers[0];
        assert.ok(layer0);
        assert.equal(layer0.rows.length, 2);
        assert.equal(layer0.id.value, 'base');
        assert.equal(layer0.mod, constants.keys_mod_none);
        for(const row of layer0.rows) {
          assert.ok(row);
          assert.equal(row.keys.length, 2);
          assert.equal(row.keys[0]?.value, 'grave');
          assert.equal(row.keys[1]?.value, 'mistake');
        }
      },
    },
    {
      subpath: 'sections/layr/error-dup-width.xml',
      errors: [
        LdmlCompilerMessages.Error_DuplicateLayerWidth({ minDeviceWidth: 120}),
      ]
    },
    ...[0, 1024, 1500, `x` as unknown as number].map(minDeviceWidth => ({
      subpath: `sections/layr/error-bad-width-${minDeviceWidth}.xml`,
      errors: [
        //
        LdmlCompilerMessages.Error_InvalidLayerWidth({ minDeviceWidth }),
      ]
    })),
  ]);
});
