'use strict';

import {isUndefined, isNull, isObject} from 'util';
import {Observable} from 'rxjs';
import {Collection} from 'blow-collection';
import {IQueryWhere, IQuery, Query, IQueryObject} from 'blow-query';
import {Adapter} from './Adapter'
import {
  IPersistedModelConstructor, 
  IPersistedAdapter,
  IModelMetadata
} from '../interfaces';

function copyObject(obj) {
  return Object.assign({}, obj);
}

export class MemoryAdapter extends Adapter implements IPersistedAdapter {

  protected _db: Map<string, Collection<any>>;

  protected _connect(): Observable<MemoryAdapter> {
    this._db = new Map();
    return Observable.of(this);
  }

  protected _collection(metadata: IModelMetadata): Collection<any> {
    const collectionName = metadata.pluralName;
    const idKey = metadata.idProperty.name;
    
    if(!this._db.has(collectionName)) {
      this._db.set(collectionName, new Collection({idKey}));
    }
    return this._db.get(collectionName);
  }
   
  protected _prepareQuery(query: IQuery | IQueryObject): IQueryObject {
    if(query) {
      if(query instanceof Query) {
        query = (<IQuery>query).toJSON();
      }
    }
    return <IQueryObject>query;
  }
  
  count(metadata: IModelMetadata, where?: IQueryWhere): Observable<number> {
    return this._collection(metadata).count(where);
  }
 
  create(metadata: IModelMetadata, data: any): Observable<any> {
    return this._collection(metadata).create(MemoryAdapter.toDB(data)).map(copyObject);
  }
  
  destroy(metadata: IModelMetadata, where?: IQueryWhere): Observable<number> {
    return this._collection(metadata).destroy(where);
  }
  
  destroyById(metadata: IModelMetadata, id: any): Observable<boolean> {
    return this._collection(metadata).destroyById(id);
  }

  exists(metadata: IModelMetadata, id: any): Observable<boolean> {
    return this._collection(metadata).exists(id);
  }
  
  find(metadata: IModelMetadata, query?: IQuery | IQueryObject): Observable<any> {
    query = this._prepareQuery(query);
    return this._collection(metadata).find(<IQueryObject>query).map(copyObject);
  }
  
  findOne(metadata: IModelMetadata, query?: IQuery | IQueryObject): Observable<any> {
    query = this._prepareQuery(query);
    return this._collection(metadata).findOne(<IQueryObject>query).map(copyObject);
  }
  
  findById(metadata: IModelMetadata, id: any): Observable<any> {
    return this._collection(metadata).findById(id).map(copyObject);
  }
  
  findOrCreate(metadata: IModelMetadata, where: IQueryWhere, data: any): Observable<any> {
    return this._collection(metadata).findOrCreate(where, data).map(copyObject);
  }
  
  update(metadata: IModelMetadata, where: IQueryWhere, data: any): Observable<number> {
    return this._collection(metadata).update(where, data);
  }
  
  updateOrCreate(metadata: IModelMetadata, data: any): Observable<any> {
    return this._collection(metadata).updateOrCreate(data).map(copyObject);
  }
  
  static toDB(data: any): any {
    return Object.assign({}, data);
  }    
  
}