'use strict';

import {Query} from 'blow-query';
import {Collection} from 'blow-collection';
import {Observable} from 'rxjs';
import {ConnectorSettings} from 'blow-service';
import {DataConnector} from './DataConnector';
import {Entity} from '../Entity';

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

export class MemoryConnector extends DataConnector {

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

  constructor(settings: ConnectorSettings) {
    super(settings);
    this._db = new Map();
  }

  protected _collection<T>(collectionName: string): Collection<T> {
    if (!this._db.has(collectionName)) {
      this._db.set(collectionName, new Collection<T>({ idKey: '_id' }));
    }
    return <Collection<T>>this._db.get(collectionName);
  }

  find<T>(collectionName: string, query?: Query): Observable<T> {
    return this._collection(collectionName)
      .find(this._prepareQuery(query)).map(copyObject);
  }

  count(collectionName: string, query?: Query): Observable<number> {
    return this._collection(collectionName)
      .count(this._prepareQuery(query).where);
  }

  delete(collectionName: string, query?: Query): Observable<number> {
    return this._collection(collectionName)
      .destroy(this._prepareQuery(query).where);
  }

  deleteById(collectionName: string, id: string): Observable<boolean> {
    return this._collection(collectionName).destroyById(id);
  }

  get<T>(collectionName: string, id: string): Observable<T> {
    return this._collection(collectionName).findById(id).map(copyObject);
  }

  save<T>(collectionName: string, doc: Entity): Observable<T> {
    const hasId = Object.keys(doc).indexOf('_id') > -1 && doc['_id'];
    if (!hasId) {
      return this._collection(collectionName).create(doc).map(copyObject);
    } else {
      return this._collection(collectionName)
        .update(this._buildQueryWhereForId(doc['_id']), doc)
        .mapTo(doc);
    }
  }

  updateAttributes<T>(collectionName: string, id: string, attributes: Entity): Observable<T> {
    delete attributes['_id'];
    return this.get<T>(collectionName, id)
      .map(result => Object.assign(result, attributes))
      .mergeMap(doc => this.save<T>(collectionName, doc));
  }
}