/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/// <reference path="../../../../node_modules/@types/ace/index.d.ts" />
import {
  Directive,
  ElementRef,
  EventEmitter,
  AfterViewInit,
  Output,
  Input,
  OnChanges,
  SimpleChanges,
  NgZone,
  OnDestroy
} from '@angular/core';

declare var ace: any;
let ACERange = ace.require('ace/range').Range;

@Directive({
  selector: '[appAceEditor]'
})

export class AlertSearchDirective implements AfterViewInit, OnChanges, OnDestroy {
  editor: AceAjax.Editor;
  closeButton: any;
  mouseEventTimer: number;
  target: any;

  @Input() text = '';
  @Output() textChanged = new EventEmitter();

  constructor(
    private elementRef: ElementRef,
    private zone: NgZone
  ) {
    this.zone.runOutsideAngular(() => {
      const el = this.elementRef.nativeElement;
      el.classList.add('editor');

      ace.config.set('basePath', 'assets/ace');

      this.editor = ace.edit(el);
      this.editor.$blockScrolling = Infinity;
      this.editor.renderer.setShowGutter(false);
      this.editor.renderer.setShowPrintMargin(false);
      this.editor.renderer.setPadding(10);
      this.editor.setTheme('ace/theme/monokai');
      this.editor.container.style.lineHeight = '1.5';

      this.editor.setOptions({
        minLines: 1,
        highlightActiveLine: false,
        maxLines: Infinity,
        fontSize: '0.75em'
      });

      this.editor.getSession().setMode('ace/mode/lucene');

      // This is a hack: setScrollMargin is not available in latest ace typings but is available in ace
      let renderer: any = this.editor.renderer;
      renderer.setScrollMargin(12, 12);

      this.closeButton = document.createElement('i');
      this.closeButton.classList.add('fa');
      this.closeButton.classList.add('fa-times');
      this.editor.on('click', (event) => {
        if (event.domEvent.target.classList.contains('fa-times')) {
          let pos = event.getDocumentPosition();
          let strToDelete = this.getTextTillOperator(event.domEvent.target.parentElement);

          let endIndex = pos.column;
          let startIndex = pos.column - (strToDelete.length + 1);
          if ( startIndex < 0) {
            startIndex = 0;
            endIndex = (strToDelete.length + 1);
          }

          let range = new ACERange(0, startIndex , 0, endIndex);
          this.editor.selection.addRange(range);
          this.editor.removeWordLeft();
          this.editor.renderer.showCursor();
          this.textChanged.next(this.editor.getValue());
        }
      });
    });
  }

  private  getTextTillOperator(valueElement) {
    let str = valueElement ? valueElement.textContent : '';

    let previousSibling = valueElement && valueElement.previousSibling;
    if (previousSibling && previousSibling.classList && previousSibling.classList.contains('ace_keyword')) {
      str = previousSibling.textContent + str;
    }

    previousSibling = previousSibling && previousSibling.previousSibling;
    if (previousSibling && previousSibling.nodeName === '#text') {
      str = previousSibling.textContent + str;
    }

    previousSibling = previousSibling && previousSibling.previousSibling;
    if (previousSibling && previousSibling.classList && previousSibling.classList.contains('ace_operator')) {
      str = previousSibling.textContent + str;
    } else {
      str = str + this.getTextTillNextOperator(valueElement);
    }

    return str;
  }

  getTextTillNextOperator(valueElement) {
    let str = '';
    let nextSibling = valueElement.nextSibling;

    if (nextSibling && nextSibling.nodeName === '#text') {
      str = str + nextSibling.textContent;
    }

    nextSibling = nextSibling && nextSibling.nextSibling;
    if (nextSibling && nextSibling.classList && nextSibling.classList.contains('ace_operator')) {
      str = str + nextSibling.textContent;
    }

    return str;
  }

  getSeacrhText(): string {
    return this.editor.getValue();
  }

  private handleMouseEvent (callback: Function) {
    clearTimeout(this.mouseEventTimer);
    this.mouseEventTimer = window.setTimeout(() => { callback(); }, 100);
  }

  private mouseover($event) {
    if ($event.target.classList.contains('ace_value') ||
        $event.target.classList.contains('ace_keyword') ||
        $event.target.classList.contains('fa-times')) {
      this.handleMouseEvent(() => {
        this.target = $event.target.classList.contains('fa-times') ? $event.target.parentElement : $event.target;
        if (this.target.classList.contains('ace_value')) {
          this.target.classList.add('active');
          if (this.target.previousSibling && this.target.previousSibling.classList) {
            this.target.previousSibling.classList.add('active');
          }
          this.target.appendChild(this.closeButton);
          this.editor.renderer.hideCursor();
        }
        if (this.target.classList.contains('ace_keyword') && !this.target.classList.contains('ace_operator')) {
          this.target.classList.add('active');
          if (this.target.nextSibling && this.target.nextSibling.classList) {
            this.target.nextSibling.classList.add('active');
            this.target.nextSibling.appendChild(this.closeButton);
          }
          this.editor.renderer.hideCursor();
        }
      });
    }
  }

  private mouseout($event) {
    if (this.target) {
      this.handleMouseEvent(() => {
        if (this.target.classList.contains('ace_value')) {
          this.target.classList.remove('active');
          if (this.target.previousSibling && this.target.previousSibling.classList) {
            this.target.previousSibling.classList.remove('active');
          }
          this.target.removeChild(this.closeButton);
          this.editor.renderer.showCursor();
        }
        if (this.target.classList.contains('ace_keyword') && !this.target.classList.contains('ace_operator')) {
          this.target.classList.remove('active');
          if (this.target.nextSibling && this.target.nextSibling.classList) {
            this.target.nextSibling.classList.remove('active');
            this.target.nextSibling.removeChild(this.closeButton);
          }
          this.editor.renderer.showCursor();
        }
        this.target = null;
      });
    }
  }

  ngAfterViewInit() {
    this.editor.getSession().setUseWrapMode(true);
    this.editor.keyBinding.addKeyboardHandler( (data, hashId, keyString, keyCode, event) => {
      if (keyCode === 13) {
        event.preventDefault();
        event.stopPropagation();
        this.textChanged.next(this.editor.getValue());
        return false;
      }
      return true;
    }, 0);
    this.elementRef.nativeElement.querySelector('.ace_content').addEventListener('mouseover', this.mouseover.bind(this));
    this.elementRef.nativeElement.querySelector('.ace_content').addEventListener('mouseout', this.mouseout.bind(this));
  }

  ngOnChanges(changes: SimpleChanges) {
    if (changes && changes['text']) {
      this.editor.setValue(this.text);
      this.editor.clearSelection();
      this.editor.focus();
    }
  }

  ngOnDestroy() {
    this.editor.destroy();
    this.editor.container.remove();
    this.editor = null;
  }
}
