'use strict';

import {Observable} from 'rxjs';
import {isString} from 'util';
import {Connection} from './Connection';
import {ModelMetadata} from './ModelMetadata';
import {
  IManager,
  IBaseModel, 
  IBaseModelConstructor,
  IPersistedModelConstructor,
  IModelMetadataOptions,
  IModelPropertyMetadataOptions,
  IModelRelationMetadataOptions,
  IModelMetadata,
  IConnection,
  IConnectionOptions
} from './interfaces';

export class Manager implements IManager {

  protected _connections: Map<string, IConnection> = new Map();
  protected _models: Map<string, IBaseModelConstructor> = new Map();

  protected _applyMetadataToModels() {
    for (const model of this._models.values()) {      
      if (!model.ready) {
        model.metadata.apply(model);
        model.ready = true;
      }
    }
  }
  
  protected _attachModelsToConnections() {
    for (const model of this._models.values()) {
      this.attachModelToConnection(model, model.metadata.connectionName);      
    }
  }
  
  attachModelToConnection(model: string | IBaseModelConstructor, connection: string | IConnection): void {
    const model_ = isString(model) ? this.getModel(<string>model) : <IBaseModelConstructor>model;
    const connection_: IConnection  = isString(connection) ? this.getConnection(<string>connection) : <IConnection>connection;
    
    if(connection_ && model_) {
      connection_.attach(model_);
    }
  }

  hasConnection(connectionName: string): boolean {
    return this._connections.has(connectionName);
  }

  getConnection(connectionName: string): IConnection {
    return this._connections.get(connectionName);
  }

  defineConnection(options: IConnectionOptions): IConnection {
    const connection = Connection.create(options);
    this._connections.set(connection.name, connection);
    return connection;
  }

  hasModel(modelName: string): boolean {
    return this._models.has(modelName);
  }

  getModel(modelName: string): IBaseModelConstructor {
    return this._models.get(modelName);
  }

  defineModel(model: IBaseModelConstructor, options: IModelMetadataOptions, properties?: {[key: string]: IModelPropertyMetadataOptions}, relations?: {[key: string]: IModelRelationMetadataOptions}): void {
    const metadata = new ModelMetadata(options, properties, relations);

    Object.defineProperty(model, '_metadata', {
      configurable: false,
      writable: false,
      enumerable: false,
      value: metadata
    });

    this._models.set(options.name, model);
  }
  
  init(): Observable<Manager> {
    return Observable.create(observer => {      

      if (this._connections.size) {
        (<Observable<IConnection>>Observable.from(this._connections.values()))
          .mergeMap(connection => {
            if (connection.isConnected) {
              return Observable.of(connection);
            } else {
              return connection.init();
            }
          })
          .toArray()
          .subscribe(
            () => { },
            error => {
              throw new Error(error);
            }, () => {
              this._attachModelsToConnections();
              this._applyMetadataToModels();
              observer.next(this);
              observer.complete();
            }
          );
      } else {
        observer.next(this);
        observer.complete();
      }
    });
  }
}

export const manager = new Manager();