'use strict';

import {Observable} from 'rxjs';
import {Query, QueryRaw, QueryWhere} from 'blow-query';
import {filter as buildFilter} from 'blow-filters';
import {isUndefined, isNumber} from 'util';

export function get(obj: any, key: string, defaultValue?: any): any {
  return obj[key] || defaultValue;
}

export function set(obj: any, key: string, value: any): any {
  obj[key] = value;
  return obj;
}

export function has(obj: any, key: string): boolean {
  return !isUndefined(get(obj, key));
}

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

export function limit<T>(data$: Observable<T>, limit: number): Observable<T> {
  if (isNumber(limit) && limit > 0) {
    return data$.take(limit);
  } else {
    return data$;
  }
}

export function skip<T>(data$: Observable<T>, skip: number): Observable<T> {
  if (isNumber(skip)) {
    return data$.skip(skip);
  } else {
    return data$;
  }
}

export function sort<T>(data$: Observable<T>, sort: { [key: string]: number }): Observable<T> {
  if (Object.keys(sort).length) {
    return data$.toArray().mergeMap(rows => {
      const fields = Object.keys(sort);
      rows = rows.sort((rowA, rowB) => {
        for (const field of fields) {
          if (rowA[field] > rowB[field]) {
            return 1 * sort[field];
          } else if (rowA[field] < rowB[field]) {
            return -1 * sort[field];
          }
        }
        return 0;
      });
      return Observable.from(rows);
    });
  } else {
    return data$;
  }
}

export function prepareQuery(query: QueryRaw | Query): QueryRaw {
  if (isUndefined(query)) {
    query = {};
  }
  if (query instanceof Query) {
    query = (<Query>query).toJSON();
  }
  return <QueryRaw>query;
}

export function where<T>(data$: Observable<T>, where: QueryWhere, invert?: boolean): Observable<T> {
  invert = invert || false;
  if (!Object.keys(where).length) {
    return data$;
  }
  let filter;
  if (invert) {
    filter = row => !buildFilter(where)(row);
  } else {
    filter = buildFilter(where);
  }
  return data$.filter(filter);
}

export function filter<T>(data$: Observable<T>, query: QueryRaw | Query): Observable<T> {
  const defaultQuery = { where: {}, sort: {}, limit: -1, skip: 0 };
  query = prepareQuery(query);

  const q: QueryRaw = Object.assign({}, defaultQuery, query);

  data$ = where(data$, q.where);
  data$ = sort(data$, q.sort);
  data$ = skip(data$, q.skip);
  data$ = limit(data$, q.limit);

  return data$;
}