'use strict';

import * as Joi from 'joi';
import {Observable} from 'rxjs';
import {isUndefined, isArray, isFunction} from 'util';
import {ModelMetadata} from './ModelMetadata';
import {ModelPropertyMetadata} from './ModelPropertyMetadata';
import {Connection} from './Connection';
import * as helpers from './helpers';
import {Validator} from './Validator';
import {IBaseModel, IValidationResult, IBaseModelData} from './interfaces';


export class BaseModel implements IBaseModel {
  
  protected _data: Map<string, any>;
  protected static _metadata: ModelMetadata;
  protected static _connection: Connection;
  static ready: boolean = false;
  static _validator: Validator;
  
  constructor(data?: IBaseModelData) {
    this._data = new Map();
    
    if(!isUndefined(data)) {
      this.merge(data);
    }
  }    

  static get metadata(): ModelMetadata {
    return this._metadata;
  }
  
  static get connection(): Connection {
    return this._connection; 
  }
  
  static get validator(): Validator {
    if(!this._validator) {
      const syncSchema = helpers.getMetadata(this).validationSchema;
      const asyncSchema = helpers.getMetadata(this).asyncValidationSchema;
      this._validator = new Validator(syncSchema, asyncSchema);
    }
    return this._validator;
  }
  
  validate(): Observable<IValidationResult> {
    return helpers.getCtor(this).validator.validate(this);
  }
  
  merge(data: IBaseModelData | IBaseModel): IBaseModel {
    const metadata = helpers.getMetadata(this);
    Object.keys(data).forEach(key => {
      if(metadata.isAllowedProperty(key)) {
        this[key] = data[key];
      }
    });
    return this;
  }
  
  toJSON(withHidden?: boolean): IBaseModelData {
    const json = {};
    withHidden = isUndefined(withHidden) ? true : withHidden;
    for(const property of helpers.getMetadata(this).properties) {
      const value = this[property.name];
      if(!isUndefined(value) && (withHidden || !property.hidden)) {
        json[property.name] = value;
      }
    }  
    for(const relation of helpers.getMetadata(this).relations) {
      if(relation.type === 'belongsTo') {
        const value = this[relation.foreignKey];
        if(!isUndefined(value) && (withHidden || !relation.hidden)) {
          json[relation.foreignKey] = value;
        }
      }
    }   
    return json;
  }
  
  toSafeJSON(): IBaseModelData {
    return this.toJSON(false);
  }
  
  inspect(): IBaseModelData {
    return this.toJSON();
  }  
 
}