'use strict';

import * as types from 'blow-types';
import {isUndefined, isObject, isFunction, isNull, isString} from 'util';
import {manager} from './manager';
import {IModelPropertyMetadataOptions, IBaseModelConstructor} from './interfaces';

export class ModelPropertyMetadata {

  protected _name: string;
  protected _type: types.Type;
  protected _default: () => any;
  protected _id: boolean;
  protected _columnName: string;
  protected _hidden: boolean;
  protected _index: boolean;
  protected _validations: Map<string, any>;

  constructor(options: IModelPropertyMetadataOptions) {
    this._name = options.name;
    this._columnName = isUndefined(options.columnName) ? options.name : options.columnName;
    this._index = isUndefined(options.index) ? false : options.index;
    this._id = isUndefined(options.id) ? false : options.id;
    this._hidden = isUndefined(options.hidden) ? false : options.hidden;
    this._validations = new Map();
    
    const type = (isString(options.type) ? {name: options.type} : options.type);    
    this._type = types.get(type);
    
    this._validations.set(type.name.toLowerCase(), true);
    
    if (!this._type) {
      this._type = types.build(manager.getModel(type.name ? type.name : type));
    }
    
    if(isObject(options.validations)) {
      Object.keys(options.validations).forEach(key => {
        this._validations.set(key, options.validations[key]);
      });
    }

    if (!isUndefined(options.default)) {
      if (isFunction(options.default)) {
        this._default = options.default;
      } else {
        this._default = () => options.default;
      }
    } else {
      this._default = undefined;
    }
  }

  get name(): string {
    return this._name;
  }

  get columnName(): string {
    return this._columnName;
  }

  get type(): types.Type {
    return this._type;
  }

  get default(): () => any {
    return this._default;
  }

  get id(): boolean {
    return this._id;
  }

  get index(): boolean {
    return this._index;
  }
  
  get hidden(): boolean {
    return this._hidden;
  }
  
  get validations(): IterableIterator<[string, any]> {
    return this._validations.entries();
  }

  apply(model: IBaseModelConstructor): void {
    const property = this;
    const getter = function(): any {
      let value = this._data.get(property.name);
      if (isUndefined(value) && property.default) {
        value = property.default.bind(this)();
        this._data.set(property.name, value);
      }
      return value;
    };
    const setter = function(value: any): void {
      value = property.type.parse(value);
      this._data.set(property.name, value);
    };

    Object.defineProperty(model.prototype, this.name, {
      configurable: false,
      enumerable: true,
      get: getter,
      set: setter
    });
  }

  static inspect(value: any): {[key: string]: any} {
    const type = types.inspect(value);
    return { type };
  }
}