import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
import { FileService } from 'common/services/file.service';
import { MessageService } from 'editor/src/app/services/message.service';
import { Unit, UnitProperties } from 'common/models/unit';
import { UIElement } from 'common/models/elements/element';
import { DropListElement } from 'common/models/elements/input-elements/drop-list';
import { StateVariable } from 'common/models/state-variable';
import { VersionManager } from 'common/services/version-manager';
import { Page } from 'common/models/page';
import { Section } from 'common/models/section';
import { SectionCounter } from 'common/util/section-counter';
import { ReferenceList, ReferenceManager } from 'editor/src/app/services/reference-manager';
import { DialogService } from '../dialog.service';
import { VeronaAPIService } from '../verona-api.service';
import { SelectionService } from '../selection.service';
import { IDService } from '../id.service';
import { UnitDefinitionSanitizer } from '../sanitizer';

@Injectable({
  providedIn: 'root'
})
export class UnitService {
  unit: Unit;
  elementPropertyUpdated: Subject<void> = new Subject<void>();
  geometryElementPropertyUpdated: Subject<string> = new Subject<string>();
  mathTableElementPropertyUpdated: Subject<string> = new Subject<string>();
  tablePropUpdated: Subject<string> = new Subject<string>();
  sectionCountUpdated: Subject<void> = new Subject<void>();
  referenceManager: ReferenceManager;
  savedSectionCode: string | undefined;
  allowExpertMode: boolean = true;
  expertMode: boolean = true;

  constructor(private selectionService: SelectionService,
              private veronaApiService: VeronaAPIService,
              private messageService: MessageService,
              private dialogService: DialogService,
              private idService: IDService) {
    this.unit = new Unit(undefined, this.idService);
    this.referenceManager = new ReferenceManager(this.unit);
  }

  loadUnitDefinition(unitDefinition: string): void {
    if (unitDefinition) {
      try {
        let unitDef = JSON.parse(unitDefinition);
        if (!VersionManager.hasCompatibleVersion(unitDef)) {
          if (VersionManager.isNewer(unitDef)) {
            throw Error('Unit-Version ist neuer als dieser Editor. Bitte mit der neuesten Version öffnen.');
          }
          if (!VersionManager.needsSanitization(unitDef)) {
            throw Error('Unit-Version ist veraltet. Sie kann mit Version 1.38/1.39 aktualisiert werden.');
          }
          this.dialogService.showSanitizationDialog().subscribe(() => {
            unitDef = UnitDefinitionSanitizer.sanitizeUnit(unitDef);
            this.loadUnit(unitDef);
            this.updateUnitDefinition();
          });
        } else {
          this.loadUnit(unitDef);
        }
      } catch (e) {
        // eslint-disable-next-line no-console
        console.error(e);
        if (e instanceof Error) this.dialogService.showUnitDefErrorDialog(e.message);
      }
    } else {
      this.unit = new Unit(undefined, this.idService);
      this.referenceManager = new ReferenceManager(this.unit);
    }
  }

  private loadUnit(parsedUnitDefinition?: string): void {
    this.idService.reset();
    this.selectionService.reset();
    this.unit = new Unit(parsedUnitDefinition as unknown as UnitProperties, this.idService);
    this.referenceManager = new ReferenceManager(this.unit);

    const invalidRefs = this.referenceManager.getAllInvalidRefs();
    if (invalidRefs.length > 0) {
      this.referenceManager.removeInvalidRefs(invalidRefs);
      this.messageService.showFixedReferencePanel(invalidRefs);
      this.updateUnitDefinition();
    }
    this.updateSectionCounter();
  }

  updateUnitDefinition(): void {
    this.veronaApiService.sendChanged(
      UnitService.createUnitDefinition(this.unit),
      `${this.unit.type}@${this.unit.version}`,
      this.unit.getVariableInfos());
  }

  private static createUnitDefinition(unit: Unit): string {
    return JSON.stringify(unit, (key, value) => {
      if (key === 'idService') {
        return undefined;
      }
      return value;
    });
  }

  saveUnit(): void {
    FileService.saveUnitToFile(UnitService.createUnitDefinition(this.unit));
  }

  async loadUnitFromFile(): Promise<void> {
    const unitFile = await FileService.loadFile(['.json']);
    this.loadUnitDefinition(unitFile.content);
  }

  /* Used by props panel to show available dropLists to connect */
  getAllDropListElementIDs(): { id: string, alias: string }[] {
    const allDropLists = this.unit.getAllElements('drop-list');
    return allDropLists.map(dropList => ({ id: dropList.id, alias: dropList.alias }));
  }

  updateStateVariables(stateVariables: StateVariable[]): void {
    this.unit.stateVariables = stateVariables;
    this.updateUnitDefinition();
  }

  /* Check references and confirm */
  prepareDelete(deletedObjectType: 'page' | 'section' | 'elements',
                object: Page | Section | UIElement[],
                pageIndex?: number): Promise<boolean> {
    return new Promise((resolve) => {
      let refs: ReferenceList[] = [];
      let dialogText: string = '';
      switch (deletedObjectType) {
        case 'page':
          refs = this.referenceManager.getPageElementsReferences(
            this.unit.pages[this.selectionService.selectedPageIndex]
          );
          const pageNavButtonRefs = this.referenceManager.getButtonReferencesForPage(
            this.selectionService.selectedPageIndex
          );
          refs = refs.concat(pageNavButtonRefs);
          if (pageIndex === undefined) throw Error();
          dialogText = `Seite ${pageIndex + 1} löschen?`;
          break;
        case 'section':
          refs = this.referenceManager.getSectionElementsReferences([object as Section]);
          dialogText = `Abschnitt ${this.selectionService.selectedSectionIndex + 1} löschen?`;
          break;
        case 'elements':
          refs = this.referenceManager.getElementsReferences(object as UIElement[]);
          dialogText = 'Folgende Elemente werden gelöscht:';
      }

      this.dialogService.showDeleteConfirmDialog(
        dialogText,
        deletedObjectType === 'elements' ? object as UIElement[] : undefined,
        refs)
        .subscribe((result: boolean) => {
          if (result) {
            if (refs.length > 0) ReferenceManager.deleteReferences(refs); // TODO rollback?
            resolve(true);
          } else {
            if (refs.length > 0) this.messageService.showReferencePanel(refs);
            resolve(false);
          }
        });
    });
  }

  updateSectionCounter(): void {
    SectionCounter.reset();
    // Wait for the change to propagate through the components
    setTimeout(() => this.sectionCountUpdated.next());
  }

  setSectionNumbering(isEnabled: boolean) {
    this.unit.enableSectionNumbering = isEnabled;
    this.updateUnitDefinition();
    this.updateSectionCounter();
  }

  setSectionNumberingPosition(position: 'above' | 'left') {
    this.unit.sectionNumberingPosition = position;
    this.updateUnitDefinition();
    this.updateSectionCounter();
  }

  setUnitNavNext(isEnabled: boolean) {
    this.unit.showUnitNavNext = isEnabled;
    this.updateUnitDefinition();
  }

  getSelectedPage() {
    return this.unit.pages[this.selectionService.selectedPageIndex];
  }

  getSelectedSection() {
    return this.unit.pages[this.selectionService.selectedPageIndex]
      .sections[this.selectionService.selectedSectionIndex];
  }

  setSectionExpertMode(checked: boolean) {
    this.expertMode = checked;
  }

  /* May remove existing connections! */
  connectAllDropLists(sectionParam?: Section) {
    const section: Section = sectionParam || this.getSelectedSection();
    const dropLists: DropListElement[] = section.getAllElements('drop-list') as DropListElement[];
    const dropListIDs = dropLists.map(list => list.id);
    dropLists.forEach(dropList => {
      dropList.connectedTo = [...dropListIDs];
      dropList.connectedTo.splice(dropListIDs.indexOf(dropList.id), 1);
    });
  }

  moveSectionToNewpage(pageIndex: number, sectionIndex: number): void {
    const sectionsLength = this.unit.pages[pageIndex].sections.length;
    const sectionsToMove = this.unit.pages[pageIndex].sections
      .splice(sectionIndex, sectionsLength - sectionIndex);

    const newPage = new Page();
    sectionsToMove.forEach(section => newPage.addSection(section));
    newPage.deleteSection(0);

    this.unit.pages.splice(pageIndex + 1, 0, newPage);
    this.selectionService.selectedPageIndex = pageIndex + 1;
    this.selectionService.selectedSectionIndex = 0;
    this.updateUnitDefinition();
  }

  collapsePage(pageIndex: number): void {
    const sectionsToMove = this.unit.pages[pageIndex].sections;
    sectionsToMove.forEach(section => this.unit.pages[pageIndex - 1].addSection(section));
    this.selectionService.selectedPageIndex = pageIndex - 1;
    this.selectionService.selectedSectionIndex = this.unit.pages[pageIndex - 1].sections.length - sectionsToMove.length;
    this.unit.deletePage(pageIndex);
    this.updateUnitDefinition();
  }
}