import {
  AfterViewInit, ApplicationRef, Component, OnInit, Renderer2, ViewChild
} from '@angular/core';
import { takeUntil } from 'rxjs/operators';
import { ElementComponent } from 'common/directives/element-component.directive';
import { VeronaSubscriptionService } from 'player/modules/verona/services/verona-subscription.service';
import {
  TextFieldSimpleComponent
} from 'common/components/compound-elements/cloze/cloze-child-elements/text-field-simple.component';
import { ClozeElement } from 'common/models/elements/compound-elements/cloze/cloze';
import { LikertElement } from 'common/models/elements/compound-elements/likert/likert';
import {
  CompoundElement, InputElement, UIElement
} from 'common/models/elements/element';
import { ButtonComponent } from 'common/components/button/button.component';
import { VeronaPostService } from 'player/modules/verona/services/verona-post.service';
import { NavigationService } from 'player/src/app/services/navigation.service';
import { AnchorService } from 'player/src/app/services/anchor.service';
import { UnitNavParam } from 'common/models/elements/button/button';
import { StateVariableStateService } from 'player/src/app/services/state-variable-state.service';
import { Subscription } from 'rxjs';
import { TextInputGroupDirective } from 'player/src/app/directives/text-input-group.directive';
import { ResponseValueType } from '@iqb/responses';
import { TableElement } from 'common/models/elements/compound-elements/table/table';
import { ImageElement } from 'common/models/elements/media-elements/image';
import { TextFieldComponent } from 'common/components/input-elements/text-field.component';
import { ImageComponent } from 'common/components/media-elements/image.component';
import { AudioComponent } from 'common/components/media-elements/audio.component';
import { AudioElement } from 'common/models/elements/media-elements/audio';
import { MediaPlayerService } from 'player/src/app/services/media-player.service';
import { NativeEventService } from 'player/src/app/services/native-event.service';
import { TextComponent } from 'common/components/text/text.component';
import { TextMarkingSupport } from 'player/src/app/classes/text-marking-support';
import { TextElement } from 'common/models/elements/text/text';
import { TextMarkingUtils } from 'player/src/app/classes/text-marking-utils';
import { MarkableSupport } from 'player/src/app/classes/markable-support';
import { UnitStateService } from '../../../services/unit-state.service';
import { ElementModelElementCodeMappingService } from '../../../services/element-model-element-code-mapping.service';
import { ValidationService } from '../../../services/validation.service';
import { KeypadService } from '../../../services/keypad.service';
import { KeyboardService } from '../../../services/keyboard.service';
import { DeviceService } from '../../../services/device.service';

@Component({
  selector: 'aspect-compound-group-element',
  templateUrl: './compound-group-element.component.html',
  styleUrls: ['./compound-group-element.component.scss']
})
export class CompoundGroupElementComponent extends TextInputGroupDirective implements OnInit, AfterViewInit {
  @ViewChild('elementComponent') elementComponent!: ElementComponent;
  ClozeElement!: ClozeElement;
  LikertElement!: LikertElement;
  TableElement!: TableElement;
  ImageElement!: ImageElement;

  isKeypadOpen: boolean = false;
  inputElement!: HTMLTextAreaElement | HTMLInputElement;
  keypadEnterKeySubscription!: Subscription;
  keypadDeleteCharactersSubscription!: Subscription;
  keypadSelectSubscription!: Subscription;

  savedPlaybackTimes: { [key: string]: number } = {};
  savedTexts: { [key: string]: string } = {};
  savedMarks: { [key: string]: string[] } = {};
  textMarkingSupports: { [key: string]: TextMarkingSupport } = {};
  markableSupports: { [key: string]: MarkableSupport } = {};

  constructor(
    public keyboardService: KeyboardService,
    public deviceService: DeviceService,
    public keypadService: KeypadService,
    public unitStateService: UnitStateService,
    public elementModelElementCodeMappingService: ElementModelElementCodeMappingService,
    public veronaSubscriptionService: VeronaSubscriptionService,
    private veronaPostService: VeronaPostService,
    private navigationService: NavigationService,
    private nativeEventService: NativeEventService,
    private anchorService: AnchorService,
    public validationService: ValidationService,
    private stateVariableStateService: StateVariableStateService,
    public mediaPlayerService: MediaPlayerService,
    private renderer: Renderer2,
    private applicationRef: ApplicationRef
  ) {
    super();
  }

  ngOnInit(): void {
    this.createForm((this.elementModel as CompoundElement).getChildElements() as InputElement[]);
    if (this.elementModel.type === 'table') {
      this.initAudioTableChildren();
      this.initTextChildren();
    }
  }

  private initTextChildren(): void {
    (this.elementModel as TableElement).elements
      .filter(child => child.type === 'text')
      .forEach(element => {
        this.setTextMarkingSupportForText(element as TextElement);
      });
  }

  private setTextMarkingSupportForText(element: TextElement): void {
    this.textMarkingSupports[element.id] = new TextMarkingSupport(this.nativeEventService, this.anchorService);
    this.markableSupports[element.id] = new MarkableSupport(this.renderer, this.applicationRef);
    this.savedMarks[element.id] = this.elementModelElementCodeMappingService
      .mapToElementModelValue(this.unitStateService
        .getElementCodeById(element.id)?.value, element) as string[];

    this.savedTexts[element.id] = (element.markingMode === 'default') ?
      TextMarkingUtils
        .restoreMarkedTextIndices(
          this.savedMarks[element.id],
          ElementModelElementCodeMappingService.modifyAnchors(element.text)) :
      ElementModelElementCodeMappingService.modifyAnchors(element.text);
  }

  private initAudioTableChildren(): void {
    (this.elementModel as TableElement).elements
      .filter(child => child.type === 'audio')
      .forEach(element => {
        this.setSavedPlaybackTimeForAudio(element);
        this.registerAtMediaPlayerService(element as AudioElement);
      });
  }

  private registerAtMediaPlayerService(audioElement: AudioElement): void {
    this.mediaPlayerService.registerMediaElement(
      audioElement.id,
      audioElement.player?.minRuns as number === 0
    );
  }

  private setSavedPlaybackTimeForAudio(element: UIElement): void {
    this.savedPlaybackTimes[element.id] = this.elementModelElementCodeMappingService.mapToElementModelValue(
      this.unitStateService.getElementCodeById(element.id)?.value, element) as number;
  }

  ngAfterViewInit(): void {
    this.registerAtUnitStateService(
      this.elementModel.id,
      null,
      this.elementComponent,
      this.pageIndex
    );
  }

  registerCompoundChildren(children: ElementComponent[]): void {
    children.forEach(child => {
      const childModel = child.elementModel;
      let initialValue: ResponseValueType;
      switch (childModel.type) {
        case 'image':
          initialValue = ElementModelElementCodeMappingService.mapToElementCodeValue(
            (this.elementModel as ImageElement).magnifierUsed, this.elementModel.type);
          break;
        case 'audio':
          initialValue = this.elementModelElementCodeMappingService.mapToElementModelValue(
            this.unitStateService.getElementCodeById(childModel.id)?.value, childModel) as number;
          break;
        case 'button':
          initialValue = null;
          break;
        case 'text':
          if (childModel.markingMode === 'word' || childModel.markingMode === 'range') {
            this.markableSupports[childModel.id]
              .createMarkables(this.savedMarks[childModel.id], child as TextComponent);
          }
          initialValue = ElementModelElementCodeMappingService.mapToElementCodeValue(
            this.getTextChildModelValue(childModel as TextElement),
            childModel.type,
            {
              markingMode: (childModel as TextElement).markingMode,
              color: 'yellow'
            });
          break;
        default:
          initialValue = ElementModelElementCodeMappingService
            .mapToElementCodeValue((childModel as InputElement).value, childModel.type);
      }
      this.registerAtUnitStateService(childModel.id, initialValue, child, this.pageIndex);
      this.registerChildEvents(child, childModel);
    });
  }

  private getTextChildModelValue(childModel: TextElement): string | string[] {
    return childModel.markingMode === 'word' || childModel.markingMode === 'range' ?
      this.savedMarks[childModel.id] :
      this.savedTexts[childModel.id];
  }

  private registerChildEvents(child: ElementComponent, childModel: UIElement): void {
    if (childModel.type === 'text-field-simple') {
      this.manageKeyInputToggling(child as TextFieldSimpleComponent);
      this.manageOnKeyDown(child as TextFieldSimpleComponent, childModel as InputElement);
    }
    if (childModel.type === 'text-field') {
      this.manageKeyInputToggling(child as TextFieldComponent);
      this.manageOnKeyDown(child as TextFieldComponent, childModel as InputElement);
    }
    if (childModel.type === 'button') {
      this.addButtonActionEventListener(child as ButtonComponent);
    }
    if (childModel.type === 'text') {
      this.addMarkingDataChangedSubscription(child as TextComponent, childModel);
      this.addTextSelectionStartSubscription(child as TextComponent, childModel);
      this.addElementCodeValueSubscription(
        child as TextComponent,
        childModel,
        { markingMode: 'word', color: 'yellow' }
      );
    }
    if (childModel.type === 'image') {
      this.addElementCodeValueSubscription(child as ImageComponent, childModel);
    }
    if (childModel.type === 'audio') {
      this.addElementCodeValueSubscription(child as AudioComponent, childModel);
      this.addMediaSubscriptions(child as AudioComponent);
    }
  }

  private addTextSelectionStartSubscription(child: TextComponent, childModel: UIElement): void {
    child.textSelectionStart
      .pipe(takeUntil(this.ngUnsubscribe))
      .subscribe(data => {
        this.textMarkingSupports[childModel.id]
          .startTextSelection(data, child);
      });
  }

  private addMarkingDataChangedSubscription(child: TextComponent, childModel: UIElement): void {
    child.markingDataChanged
      .pipe(takeUntil(this.ngUnsubscribe))
      .subscribe(data => this.textMarkingSupports[childModel.id]
        .applyMarkingData(data, child));
  }

  private addMediaSubscriptions(child: AudioComponent): void {
    child.mediaValidStatusChanged
      .pipe(takeUntil(this.ngUnsubscribe))
      .subscribe(mediaId => {
        this.mediaPlayerService.setValidStatusChanged(mediaId);
      });

    child.mediaPlayStatusChanged
      .pipe(takeUntil(this.ngUnsubscribe))
      .subscribe(mediaId => {
        this.mediaPlayerService.setActualPlayingId(mediaId);
      });
  }

  private addElementCodeValueSubscription(
    child: ImageComponent | AudioComponent | TextComponent,
    childModel: UIElement,
    options?: Record<string, string>
  ): void {
    child.elementValueChanged
      .pipe(takeUntil(this.ngUnsubscribe))
      .subscribe(value => {
        this.unitStateService.changeElementCodeValue({
          id: value.id,
          value: ElementModelElementCodeMappingService
            .mapToElementCodeValue(value.value, childModel.type, options)
        });
      });
  }

  private manageOnKeyDown(
    textInputComponent: TextFieldSimpleComponent | TextFieldComponent,
    elementModel: InputElement): void {
    textInputComponent
      .onKeyDown
      .pipe(takeUntil(this.ngUnsubscribe))
      .subscribe(event => {
        this.onKeyDown(event, elementModel);
      });
  }

  private manageKeyInputToggling(textInputComponent: TextFieldSimpleComponent | TextFieldComponent): void {
    textInputComponent
      .focusChanged
      .pipe(takeUntil(this.ngUnsubscribe))
      .subscribe(focusedTextInput => {
        this.toggleKeyInput(focusedTextInput, textInputComponent);
      });
  }

  private onKeyDown(event: {
    keyboardEvent: KeyboardEvent;
    inputElement: HTMLInputElement | HTMLTextAreaElement
  }, elementModel: InputElement): void {
    this.detectHardwareKeyboard(elementModel);
    this.checkInputLimitation(event, elementModel);
  }

  private addButtonActionEventListener(button: ButtonComponent) {
    button.buttonActionEvent
      .pipe(takeUntil(this.ngUnsubscribe))
      .subscribe(buttonEvent => {
        switch (buttonEvent.action) {
          case 'unitNav':
            this.veronaPostService.sendVopUnitNavigationRequestedNotification(
              (buttonEvent.param as UnitNavParam)
            );
            break;
          case 'pageNav':
            this.navigationService.setPage(buttonEvent.param as number);
            break;
          case 'highlightText':
            this.anchorService.toggleAnchor(buttonEvent.param as string);
            break;
          case 'stateVariableChange':
            this.stateVariableStateService.changeElementCodeValue(
              buttonEvent.param as { id: string, value: string }
            );
            break;
          default:
        }
      });
  }
}