/*
 * Copyright (C) 2019 StApps
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <https://www.gnu.org/licenses/>.
 */
import {expect} from 'chai';
import clone from 'rfdc';
import {SCThingRemoteOrigin} from '../src/index.js';
import {SCDishMeta} from '../src/index.js';
import {SCThingTranslator} from '../src/index.js';
import {dish} from './dummy/dish.js';
import {setting} from './dummy/setting.js';

const translator = new SCThingTranslator('de');
const translatorEN = new SCThingTranslator('en');
// this will simulate a translator always utilizing the base language translations
const translatorWithFallback = new SCThingTranslator('tt');

const translatedThingDE = translator.translate(dish);
const translatedThingFallback = translatorWithFallback.translate(dish);

describe('Translator', function () {
  this.timeout(10_000);
  this.slow(5000);

  describe('direct', function () {
    it('should translate enum single value', function () {
      expect(translator.translatedAccess(setting).inputType).to.equal('einfache Auswahl');
    });

    it('should translate string literal type', function () {
      expect(translator.translatedAccess(dish).type).to.equal('Essen');
      expect(translatedThingDE.type).to.equal('Essen');
    });

    it('should translate string property', function () {
      expect(translator.translatedAccess(dish).name).to.equal('de-dish-name');
      expect(translatedThingDE.name).to.equal('de-dish-name');
    });

    it('should translate array of strings', function () {
      expect(translator.translatedAccess(dish).characteristics).to.deep.equal([
        {name: 'de-characteristic0'},
        {name: 'de-characteristic1'},
      ]);
      expect(translatedThingDE.characteristics).to.deep.equal([
        {name: 'de-characteristic0'},
        {name: 'de-characteristic1'},
      ]);
    });

    it('should translate array of strings subscript', function () {
      expect(translator.translatedAccess(dish).characteristics?.[1]).to.deep.equal({
        name: 'de-characteristic1',
      });
      expect(translatedThingDE.characteristics![1]).to.deep.equal({name: 'de-characteristic1'});
    });

    it('should translate meta array of string', function () {
      expect(translator.translatedAccess(dish).categories).to.deep.equal(['Hauptgericht', 'Nachtisch']);
      expect(translatedThingDE.categories).to.deep.equal(['Hauptgericht', 'Nachtisch']);
    });

    it('should translate meta array of strings subscript', function () {
      expect(translator.translatedAccess(dish).categories[1]).to.equal('Nachtisch');
      expect(translatedThingDE.categories[1]).to.equal('Nachtisch');
    });
  });

  describe('nested', function () {
    it('should translate string literal type', function () {
      expect(translator.translatedAccess(dish).offers?.[0].inPlace?.type).to.equal('Gebäude');
      expect(translatedThingDE.offers![0].inPlace!.type).to.equal('Gebäude');
    });

    it('should translate nested string property', function () {
      expect(translator.translatedAccess(dish).offers?.[0].inPlace?.name).to.equal('de-space-name');
      expect(translatedThingDE.offers![0].inPlace!.name).to.equal('de-space-name');
    });

    it('should translate meta array of strings', function () {
      expect(translator.translatedAccess(dish).offers?.[0].inPlace?.categories).to.deep.equal([
        'Büro',
        'Bildung',
      ]);
      expect(translatedThingDE.offers![0].inPlace!.categories).to.deep.equal(['Büro', 'Bildung']);
    });

    it('should translate meta array of strings subscript', function () {
      expect(translator.translatedAccess(dish).offers?.[0].inPlace?.categories[1]).to.equal('Bildung');
      expect(translatedThingDE.offers![0].inPlace!.categories[1]).to.equal('Bildung');
    });
  });

  describe('direct (fallback)', function () {
    it('should translate string literal types', function () {
      expect(translatorWithFallback.translatedAccess(dish).type).to.equal('dish');
      expect(translatedThingFallback.type).to.equal('dish');
    });

    it('should translate string property', function () {
      expect(translatorWithFallback.translatedAccess(dish).name).to.equal('base-dish-name');
      expect(translatedThingFallback.name).to.equal('base-dish-name');
    });

    it('should translate array of strings subscript', function () {
      expect(translatorWithFallback.translatedAccess(dish).characteristics?.[1]).to.deep.equal({
        name: 'base-characteristic1',
      });
      expect(translatedThingFallback.characteristics![1]).to.deep.equal({name: 'base-characteristic1'});
    });

    it('should translate meta array of strings', function () {
      expect(translatorWithFallback.translatedAccess(dish).categories).to.deep.equal([
        'main dish',
        'dessert',
      ]);
      expect(translatedThingFallback.categories).to.deep.equal(['main dish', 'dessert']);
    });

    it('should translate meta array of string subscript', function () {
      expect(translatorWithFallback.translatedAccess(dish).categories[1]).to.equal('dessert');
      expect(translatedThingFallback.categories[1]).to.equal('dessert');
    });
  });

  describe('nested (fallback)', function () {
    it('should translate string literal type', function () {
      expect(translatorWithFallback.translatedAccess(dish).offers?.[0].inPlace?.type).to.equal('building');
      expect(translatedThingFallback.offers![0].inPlace!.type).to.equal('building');
    });

    it('should translate string property', function () {
      expect(translatorWithFallback.translatedAccess(dish).offers?.[0].inPlace?.name).to.equal(
        'base-space-name',
      );
      expect(translatedThingFallback.offers![0].inPlace!.name).to.equal('base-space-name');
    });

    it('should translate meta array of string', function () {
      expect(translatorWithFallback.translatedAccess(dish).offers?.[0].inPlace?.categories).to.deep.equal([
        'office',
        'education',
      ]);
      expect(translatedThingFallback.offers![0].inPlace!.categories).to.deep.equal(['office', 'education']);
    });

    it('should translate meta array of strings subscript', function () {
      expect(translatorWithFallback.translatedAccess(dish).offers?.[0].inPlace?.categories[1]).to.equal(
        'education',
      );
      expect(translatedThingFallback.offers![0].inPlace!.categories[1]).to.equal('education');
    });
  });

  it('should omit LRU cache with changed source', function () {
    const translatorDE = new SCThingTranslator('de');
    const dishCopy = clone()(dish);
    const translatedDish = translatorDE.translatedAccess(dish);
    const destructivelyTranslatedDish = translatorDE.translate(dish);

    (dishCopy.origin as SCThingRemoteOrigin).name = 'translator.spec';
    expect(translatorDE.translatedAccess(dishCopy)).not.to.deep.equal(translatedDish);
    expect(translatorDE.translate(dishCopy)).not.to.equal(destructivelyTranslatedDish);
  });

  it('should flush its LRU cache with changed translator language', function () {
    const translatorDE = new SCThingTranslator('de');
    expect(translatorDE.translatedAccess(dish).name).to.equal('de-dish-name');
    expect(translatorDE.translate(dish).name).to.equal('de-dish-name');
    translatorDE.language = 'en';
    expect(translatorDE.translatedAccess(dish).name).to.equal('base-dish-name');
    expect(translatorDE.translate(dish).name).to.equal('base-dish-name');
  });

  it('should force translator LRU cache to overflow', function () {
    const translatorDE = new SCThingTranslator('de');
    // Make sure to add more elements to the translator cache than the maximum cache capacity. See Translator.ts
    for (let i = 0; i < 201; i++) {
      const anotherDish = Object.assign({}, dish);
      anotherDish.uid = String(i);
      expect(translatorDE.translatedAccess(anotherDish).name).to.equal('de-dish-name');
    }
  });
});

describe('MetaTranslator', function () {
  this.timeout(10_000);
  this.slow(5000);

  it('should have consistency with meta class', function () {
    const dishMetaTranslationsDE = translator.translatedPropertyNames(dish.type);
    const dishMetaTranslationsEN = translatorEN.translatedPropertyNames(dish.type);
    expect(dishMetaTranslationsEN).to.not.deep.equal(dishMetaTranslationsDE);
    expect(dishMetaTranslationsDE).to.deep.equal(new SCDishMeta().fieldTranslations.de);
    expect(dishMetaTranslationsEN).to.deep.equal(new SCDishMeta().fieldTranslations.en);
  });

  it('should retrieve translated property value type', function () {
    const dishTypeDE = translator.translatedPropertyValue(dish.type, 'type');
    const dishTypeEN = translatorEN.translatedPropertyValue(dish.type, 'type', undefined);
    const dishTypeBASE = translatorWithFallback.translatedPropertyValue(dish.type, 'type');
    expect(dishTypeDE).to.deep.equal(new SCDishMeta().fieldValueTranslations.de.type);
    expect(dishTypeEN).to.deep.equal(new SCDishMeta().fieldValueTranslations.en.type);
    expect(dishTypeBASE).to.deep.equal(new SCDishMeta().fieldValueTranslations.en.type);
  });

  it('should retrieve translated property value nested', function () {
    const dishTypeDE = translator.translatedPropertyValue(dish.type, 'categories', 'main dish');
    const dishTypeEN = translatorEN.translatedPropertyValue(dish.type, 'categories', 'main dish');
    const dishTypeBASE = translatorWithFallback.translatedPropertyValue(dish.type, 'categories', 'main dish');
    expect(dishTypeDE).to.deep.equal(new SCDishMeta().fieldValueTranslations.de.categories['main dish']);
    expect(dishTypeEN).to.deep.equal(dish.categories[0]);
    expect(dishTypeBASE).to.deep.equal(dish.categories[0]);
  });

  it('should translate thing without meta class', function () {
    const dishCopy = clone()(dish);
    const typeNonExistent = eval("(x) => x + 'typeNonExistent';");
    // this will assign a non-existent SCThingType to dishCopy
    dishCopy.type = typeNonExistent();
    const dishMetaTranslationsDE = translator.translatedPropertyNames(dishCopy.type);
    expect(dishMetaTranslationsDE).to.be.undefined;
  });
});
