import {
  forwardRef,
  Component,
  HostBinding,
  Input,
  SimpleChange,
  ViewChild,
  ElementRef,
  EventEmitter,
  Output
} from '@angular/core';
import {
  NG_VALUE_ACCESSOR
} from '@angular/forms';
import { BooleanFieldValue } from './../annotations/field-value';
import { Observable } from 'rxjs/Observable';


const noop = () => {
};

export const EN_INPUT_CONTROL_VALUE_ACCESSOR: any = {
  provide:     NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => EnInputComponent),
  multi:       true
};

// Invalid input type. Using one of these will throw an MdInputUnsupportedTypeError.
const EN_INPUT_INVALID_INPUT_TYPE = [
  'file',
  'radio',
  'checkbox',
];

@Component({
  selector:   'en-input',
  template:   require('./en-input.component.html'),
  styles:     [require('./en-input.component.scss')],
  providers:  [EN_INPUT_CONTROL_VALUE_ACCESSOR]
})
export class EnInputComponent {
  private _focused: boolean = false;
  private _value: any       = '';

  /** Callback registered via registerOnTouched (ControlValueAccessor) */
  private _onTouchedCallback: () => void      = noop;
  /** Callback registered via registerOnChange (ControlValueAccessor) */
  private _onChangeCallback: (_: any) => void = noop;

  /**
   * Aria related inputs.
   */
  @Input('aria-label') ariaLabel: string;
  @Input('aria-labelledby') ariaLabelledBy: string;
  @Input('aria-disabled') ariaDisabled: boolean;
  @Input('aria-required') ariaRequired: boolean;
  @Input('aria-invalid') ariaInvalid: boolean;

  // /**
  //  * Content directives.
  //  */
  // @ContentChild(MdPlaceholder) _placeholderChild: MdPlaceholder;
  // @ContentChildren(MdHint) _hintChildren: QueryList<MdHint>;

  /** Readonly properties. */
  get focused() {
    return this._focused;
  }

  get empty() {
    return this._value == null || this._value === '';
  }

  get characterCount(): number {
    return this.empty ? 0 : ('' + this._value).length;
  }

  get inputId(): string {
    return `${this.id}`;
  }

  get floatingPlaceholder(): boolean {
    return this._focused || !this.empty;
  }

  /**
   * Bindings.
   */
  @Input() align: 'start' | 'end' = 'start';
  // @Input() floatingPlaceholder: boolean = true;

  @Input() autoComplete: string;
  @Input() autoCorrect: string;
  @Input() autoCapitalize: string;
  @Input() @BooleanFieldValue() autoFocus: boolean  = false;
  @Input() @BooleanFieldValue() disabled: boolean   = false;
  @Input() id: string                               = ``;
  @Input() list: string                             = null;
  @Input() max: string | number                     = null;
  @Input() maxLength: number                        = null;
  @Input() min: string | number                     = null;
  @Input() minLength: number                        = null;
  @Input() placeholder: string                      = null;
  @Input() @BooleanFieldValue() readOnly: boolean   = false;
  @Input() @BooleanFieldValue() required: boolean   = false;
  @Input() @BooleanFieldValue() spellCheck: boolean = false;
  @Input() step: number                             = null;
  @Input() tabIndex: number                         = null;
  @Input() type: string                             = 'text';
  @Input() name: string                             = null;
  @Input() @BooleanFieldValue() addonRight: boolean  = false;

  private _blurEmitter: EventEmitter<FocusEvent>  = new EventEmitter<FocusEvent>();
  private _focusEmitter: EventEmitter<FocusEvent> = new EventEmitter<FocusEvent>();

  @Output('blur')
  get onBlur(): Observable<FocusEvent> {
    return this._blurEmitter.asObservable();
  }

  @Output('focus')
  get onFocus(): Observable<FocusEvent> {
    return this._focusEmitter.asObservable();
  }

  get value(): any {
    return this._value;
  };

  @Input() set value(v: any) {
    v = this._convertValueForInputType(v);
    if (v !== this._value) {
      this._value = v;
      this._onChangeCallback(v);
    }
  }

  @Output() btnClick: EventEmitter<any> = new EventEmitter();

  _btnClickHandler(): void {
    this.btnClick.emit({
      value: this.value
    });
  }

  // This is to remove the `align` property of the `md-input` itself. Otherwise HTML5
  // might place it as RTL when we don't want to. We still want to use `align` as an
  // Input though, so we use HostBinding.
  @HostBinding('attr.align') get _align(): any {
    return null;
  }


  @ViewChild('input') _inputElement: ElementRef;

  /** Set focus on input */
  focus() {
    this._inputElement.nativeElement.focus();
  }

  _handleFocus(event: FocusEvent) {
    this._focused = true;
    this._focusEmitter.emit(event);
  }

  _handleBlur(event: FocusEvent) {
    this._focused = false;
    this._onTouchedCallback();
    this._blurEmitter.emit(event);
  }

  _handleChange(event: Event) {
    this.value = (<HTMLInputElement>event.target).value;
    this._onTouchedCallback();
  }

  _labelClickHandler() {
    if (!this.floatingPlaceholder) {
      this._inputElement.nativeElement.focus();
    }
  }

  // _hasPlaceholder(): boolean {
  //   return !!this.placeholder || this._placeholderChild != null;
  // }

  /**
   * Implemented as part of ControlValueAccessor.
   * TODO: internal
   */
  writeValue(value: any) {
    this._value = value;
  }

  /**
   * Implemented as part of ControlValueAccessor.
   * TODO: internal
   */
  registerOnChange(fn: any) {
    this._onChangeCallback = fn;
  }

  /**
   * Implemented as part of ControlValueAccessor.
   * TODO: internal
   */
  registerOnTouched(fn: any) {
    this._onTouchedCallback = fn;
  }

  /** TODO: internal */
  ngAfterContentInit() {
    this._validateConstraints();

    // Trigger validation when the hint children change.
    // this._hintChildren.changes.subscribe(() => {
    //   this._validateConstraints();
    // });
  }

  /** TODO: internal */
  ngOnChanges(changes: {[key: string]: SimpleChange}) {
    this._validateConstraints();
  }

  /**
   * Convert the value passed in to a value that is expected from the type of the md-input.
   * This is normally performed by the *_VALUE_ACCESSOR in forms, but since the type is bound
   * on our internal input it won't work locally.
   * @private
   */
  private _convertValueForInputType(v: any): any {
    switch (this.type) {
      case 'number':
        return parseFloat(v);
      default:
        return v;
    }
  }

  /**
   * Ensure that all constraints defined by the API are validated, or throw errors otherwise.
   * Constraints for now:
   *   - type attribute is not one of the forbidden types (see constant at the top).
   * @private
   */
  private _validateConstraints() {
    if (EN_INPUT_INVALID_INPUT_TYPE.indexOf(this.type) != -1) {
      throw new TypeError(`${this.type} is not a valid en-input type.`);
    }
  }
}
