// *****************************************************************************
// Copyright (C) 2019 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { enableJSDOM } from '../test/jsdom';

let disableJSDOM = enableJSDOM();

import { Container, injectable } from 'inversify';
import type { IMacKeyboardLayoutInfo } from 'native-keymap';
import * as chai from 'chai';
import * as sinon from 'sinon';
import * as os from '../../common/os';
import { ILogger, Loggable } from '../../common/logger';
import { LocalStorageService } from '../storage-service';
import { MessageService } from '../../common/message-service';
import { WindowService } from '../window/window-service';
import { BrowserKeyboardLayoutProvider } from './browser-keyboard-layout-provider';
import { Key } from './keys';

disableJSDOM();

describe('browser keyboard layout provider', function (): void {

    let stubOSX: sinon.SinonStub;
    let stubWindows: sinon.SinonStub;

    before(() => disableJSDOM = enableJSDOM());
    after(() => disableJSDOM());

    const setup = (system: 'mac' | 'win' | 'linux') => {
        switch (system) {
            case 'mac':
                stubOSX = sinon.stub(os, 'isOSX').value(true);
                stubWindows = sinon.stub(os, 'isWindows').value(false);
                break;
            case 'win':
                stubOSX = sinon.stub(os, 'isOSX').value(false);
                stubWindows = sinon.stub(os, 'isWindows').value(true);
                break;
            default:
                stubOSX = sinon.stub(os, 'isOSX').value(false);
                stubWindows = sinon.stub(os, 'isWindows').value(false);
        }
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const container = new Container();
        container.bind(BrowserKeyboardLayoutProvider).toSelf();
        container.bind(ILogger).to(MockLogger);
        container.bind(LocalStorageService).toSelf().inSingletonScope();
        container.bind(MessageService).toConstantValue({} as MessageService);
        container.bind(WindowService).toConstantValue({} as WindowService);
        const service = container.get(BrowserKeyboardLayoutProvider);
        return { service, container };
    };

    afterEach(() => {
        stubOSX.restore();
        stubWindows.restore();
    });

    it('detects German Mac layout', async () => {
        const { service } = setup('mac');
        let currentLayout = await service.getNativeLayout();
        service.onDidChangeNativeLayout(l => {
            currentLayout = l;
        });

        chai.expect((currentLayout.info as IMacKeyboardLayoutInfo).id).to.equal('com.apple.keylayout.US');
        service.validateKey({ code: Key.SEMICOLON.code, character: 'ö' });
        chai.expect((currentLayout.info as IMacKeyboardLayoutInfo).id).to.equal('com.apple.keylayout.German');
    });

    it('detects French Mac layout', async () => {
        const { service } = setup('mac');
        let currentLayout = await service.getNativeLayout();
        service.onDidChangeNativeLayout(l => {
            currentLayout = l;
        });

        chai.expect((currentLayout.info as IMacKeyboardLayoutInfo).id).to.equal('com.apple.keylayout.German');
        service.validateKey({ code: Key.SEMICOLON.code, character: 'm' });
        chai.expect((currentLayout.info as IMacKeyboardLayoutInfo).id).to.equal('com.apple.keylayout.French');
    });

    it('detects keyboard layout change', async () => {
        const { service } = setup('mac');
        let currentLayout = await service.getNativeLayout();
        service.onDidChangeNativeLayout(l => {
            currentLayout = l;
        });

        service.validateKey({ code: Key.QUOTE.code, character: 'ä' });
        service.validateKey({ code: Key.SEMICOLON.code, character: 'ö' });
        service.validateKey({ code: Key.BRACKET_LEFT.code, character: 'ü' });
        chai.expect((currentLayout.info as IMacKeyboardLayoutInfo).id).to.equal('com.apple.keylayout.German');
        service.validateKey({ code: Key.SEMICOLON.code, character: 'm' });
        chai.expect((currentLayout.info as IMacKeyboardLayoutInfo).id).to.equal('com.apple.keylayout.French');
    });

    it('applies layout chosen by the user', async () => {
        const { service } = setup('mac');
        let currentLayout = await service.getNativeLayout();
        service.onDidChangeNativeLayout(l => {
            currentLayout = l;
        });

        service.validateKey({ code: Key.SEMICOLON.code, character: 'm' });
        const spanishLayout = service.allLayoutData.find(data => data.name === 'Spanish' && data.hardware === 'mac')!;
        await service.setLayoutData(spanishLayout);
        chai.expect((currentLayout.info as IMacKeyboardLayoutInfo).id).to.equal('com.apple.keylayout.Spanish');
        await service.setLayoutData('autodetect');
        chai.expect((currentLayout.info as IMacKeyboardLayoutInfo).id).to.equal('com.apple.keylayout.French');
    });

    it('restores pressed keys from last session', async () => {
        const { service, container } = setup('mac');

        service.validateKey({ code: Key.SEMICOLON.code, character: 'm' });
        const service2 = container.get(BrowserKeyboardLayoutProvider);
        chai.expect(service2).to.not.equal(service);
        const currentLayout = await service2.getNativeLayout();
        chai.expect((currentLayout.info as IMacKeyboardLayoutInfo).id).to.equal('com.apple.keylayout.French');
    });

    it('restores user selection from last session', async () => {
        const { service, container } = setup('mac');

        const spanishLayout = service.allLayoutData.find(data => data.name === 'Spanish' && data.hardware === 'mac')!;
        await service.setLayoutData(spanishLayout);
        const service2 = container.get(BrowserKeyboardLayoutProvider);
        chai.expect(service2).to.not.equal(service);
        service2.validateKey({ code: Key.SEMICOLON.code, character: 'm' });
        const currentLayout = await service2.getNativeLayout();
        chai.expect((currentLayout.info as IMacKeyboardLayoutInfo).id).to.equal('com.apple.keylayout.Spanish');
    });

});

@injectable()
class MockLogger implements Partial<ILogger> {
    trace(loggable: Loggable): Promise<void> {
        return Promise.resolve();
    }
    debug(loggable: Loggable): Promise<void> {
        return Promise.resolve();
    }
    info(loggable: Loggable): Promise<void> {
        return Promise.resolve();
    }
    warn(loggable: Loggable): Promise<void> {
        return Promise.resolve();
    }
    error(loggable: Loggable): Promise<void> {
        return Promise.resolve();
    }
    fatal(loggable: Loggable): Promise<void> {
        return Promise.resolve();
    }
}
