/**
 * @providesModule Firestack
 * @flow
 */
import { NativeModules, NativeEventEmitter } from 'react-native';

import Log from './utils/log';
import { promisify } from './utils';
import Singleton from './utils/singleton';

// modules
import Auth from './modules/auth';
import Storage from './modules/storage';
import Database from './modules/database';
import Presence from './modules/presence';
import Messaging from './modules/messaging';
import Analytics from './modules/analytics';
import RemoteConfig from './modules/remoteConfig';

let log;
const instances = { default: null };
const FirestackModule = NativeModules.Firestack;
const FirestackModuleEvt = new NativeEventEmitter(FirestackModule);

type GoogleApiAvailabilityType = {
  status: number,
  isAvailable: boolean,
  isUserResolvableError?: boolean,
  error?: string
};

/**
 * @class Firestack
 */
export default class Firestack extends Singleton {

  /**
   *
   * @param options
   */
  constructor(options: Object = {}) {
    const instance = super(options);

    instance.options = Object.assign({ errorOnMissingPlayServices: true }, options);
    instance._debug = instance.options.debug || false;

    Log.enable(instance._debug);
    log = instance._log = new Log('firestack');

    log.info('Creating new firestack instance');

    instance._remoteConfig = instance.options.remoteConfig || {};
    delete instance.options.remoteConfig;

    instance.configured = instance.options.configure || false;

    instance.eventHandlers = {};

    log.info('Calling configure with options', instance.options);
    instance.configurePromise = instance.configure(instance.options);

    instance._auth = new Auth(instance, instance.options);

    if (instance.options.errorOnMissingPlayServices && !this.googleApiAvailability.isAvailable) {
      throw new Error(`Google Play Services is required to run this application but no valid installation was found (Code ${this.googleApiAvailability.status}).`);
    }
  }

  _db: ?Object;
  _log: ?Object;
  _auth: ?Object;
  _store: ?Object;
  _storage: ?Object;
  _presence: ?Object;
  _analytics: ?Object;
  _constants: ?Object;
  _messaging: ?Object;
  _remoteConfig: ?Object;

  /**
   * Support web version of initApp.
   * @param options
   * @param name
   * @returns {*}
   */
  static initializeApp(options: Object = {}, name: string = 'default') {
    if (!instances[name]) instances[name] = new Firestack(options);
    return instances[name];
  }


  /**
   *
   * @param opts
   * @returns {Promise.<TResult>|*|Promise.<T>}
   */
  configure(opts: Object = {}) {
    if (!this.configurePromise) {
      const firestackOptions = Object.assign({}, this.options, opts);

      this.configurePromise = promisify('configureWithOptions', FirestackModule)(firestackOptions)
        .then((configuredProperties) => {
          log.info('Native configureWithOptions success', configuredProperties);
          this.configured = true;
          this.firestackOptions = configuredProperties;
          return configuredProperties;
        }).catch((err) => {
          log.info('Native error occurred while calling configure', err);
        });
    }
    return this.configurePromise;
  }

  onReady(cb: Function) {
    // TODO wut o.O
    return this.configurePromise = this.configurePromise.then(cb);
  }

  /**
   * Wrappers
   * We add methods from each wrapper to this instance
   * when they are needed. Not sure if this is a good
   * idea or not (imperative vs. direct manipulation/proxy)
   */
  auth() {
    return this._auth;
  }


  database() {
    if (!this._db) {
      this._db = new Database(this);
    }
    return this._db;
  }


  analytics() {
    if (!this._analytics) {
      this._analytics = new Analytics(this);
    }
    return this._analytics;
  }

  // storage
  storage() {
    if (!this._storage) {
      this._storage = new Storage(this);
    }
    return this._storage;
  }

  presence() {
    if (!this._presence) {
      this._presence = new Presence(this);
    }
    return this._presence;
  }

  messaging() {
    if (!this._messaging) {
      this._messaging = new Messaging(this);
    }
    return this._messaging;
  }

  remoteConfig() {
    if (!this._remoteConfig) {
      this._remoteConfig = new RemoteConfig(this);
    }
    return this._remoteConfig;
  }

  // // TODO remove as deprecated and its in the wrong place anyway
  get ServerValue(): Promise<*> {
    return promisify('serverValue', FirestackModule)();
  }

  // TODO what are these for?
  get app(): Object {
    return this.appInstance;
  }

  // TODO what are these for?
  getInstance(): Object {
    return this.appInstance;
  }

  get apps(): Array<string> {
    return Object.keys(instances);
  }

  /**
   * Returns androids GoogleApiAvailability status and message if available.
   * @returns {GoogleApiAvailabilityType|{isAvailable: boolean, status: number}}
   */
  get googleApiAvailability(): GoogleApiAvailabilityType {
    // if not available then return a fake object for ios - saves doing platform specific logic.
    return FirestackModule.googleApiAvailability || { isAvailable: true, status: 0 };
  }

  /**
   * Logger
   */
  get log(): Log {
    return this._log;
  }

  /**
   * Redux store
   **/
  get store(): ?Object {
    return this._store;
  }

  get constants(): Object {
    if (!this._constants) {
      this._constants = Object.assign({}, Storage.constants)
    }
    return this._constants;
  }

  /**
   * Set the redux store helper
   */
  setStore(store: Object) {
    if (store) {
      this.log.info('Setting the store for Firestack instance');
      this._store = store;
    }
  }

  /**
   * Global event handlers for the single Firestack instance
   */
  on(name: string, cb: Function, nativeModule: Object = FirestackModuleEvt) {
    if (!this.eventHandlers[name]) {
      this.eventHandlers[name] = [];
    }

    const sub = nativeModule.addListener(name, cb);
    this.eventHandlers[name].push(sub);
    return sub;
  }

  off(name: string) {
    if (this.eventHandlers[name]) {
      this.eventHandlers[name]
        .forEach(subscription => subscription.remove());
    }
  }
}

@@ -1963,24 +1963,265 @@
 %7D).%60);%0A    %7D
+%0A%0A    const database = %7B%0A      ServerValue: %7B%0A        TIMESTAMP: FirestackModule.serverValueTimestamp %7C%7C %7B '.sv': 'timestamp' %7D,%0A      %7D%0A    %7D;%0A    database.__proto__ = instance.database.__proto__;%0A    instance.database.__proto__ = database;
 %0A  %7D%0A%0A  _db:

Add support for firestack.ServerValue.TIMESTAMP