[CORD-873] CRUD for Core and Service model from Chameleon
Change-Id: I45c533feba6720b82de3681d862773047e7fd6f8
diff --git a/src/app/datasources/helpers/model-discoverer.service.ts b/src/app/datasources/helpers/model-discoverer.service.ts
new file mode 100644
index 0000000..deaed33
--- /dev/null
+++ b/src/app/datasources/helpers/model-discoverer.service.ts
@@ -0,0 +1,258 @@
+// TODO test me hard!!!
+
+import * as _ from 'lodash';
+import {IXosModeldefsService, IXosModeldef, IXosModelDefsField, IXosModelDefsRelation} from '../rest/modeldefs.rest';
+import {IXosTableCfg} from '../../core/table/table';
+import {IXosFormCfg} from '../../core/form/form';
+import {IXosNavigationService} from '../../core/services/navigation';
+import {IXosConfigHelpersService} from '../../core/services/helpers/config.helpers';
+import {IXosRuntimeStatesService, IXosState} from '../../core/services/runtime-states';
+import {IXosModelStoreService} from '../stores/model.store';
+
+export interface IXosModel {
+  name: string; // the model name
+  app: string; // the service to wich it belong
+  fields: IXosModelDefsField[];
+  relations?: IXosModelDefsRelation[];
+  backendUrl?: string; // the api endpoint
+  clientUrl?: string; // the view url
+  tableCfg?: IXosTableCfg;
+  formCfg?: IXosFormCfg;
+}
+
+// Service
+export interface IXosModelDiscovererService {
+  discover(): ng.IPromise<boolean>;
+  get(modelName: string): IXosModel;
+}
+
+export class XosModelDiscovererService implements IXosModelDiscovererService {
+  static $inject = [
+    '$log',
+    '$q',
+    'XosModelDefs',
+    'ConfigHelpers',
+    'XosRuntimeStates',
+    'XosNavigationService',
+    'XosModelStore'
+  ];
+  private xosModels: IXosModel[] = []; // list of augmented model definitions;
+  private xosServices: string[] = []; // list of loaded services
+
+  constructor (
+    private $log: ng.ILogService,
+    private $q: ng.IQService,
+    private XosModelDefs: IXosModeldefsService,
+    private ConfigHelpers: IXosConfigHelpersService,
+    private XosRuntimeStates: IXosRuntimeStatesService,
+    private XosNavigationService: IXosNavigationService,
+    private XosModelStore: IXosModelStoreService
+  ) {
+  }
+
+  public get(modelName: string): IXosModel|null {
+    return _.find(this.xosModels, m => m.name === modelName);
+  }
+
+  public discover() {
+    const d = this.$q.defer();
+
+    this.XosModelDefs.get()
+      .then((modelsDef: IXosModeldef[]) => {
+
+        const pArray = [];
+        _.forEach(modelsDef, (model: IXosModeldef) => {
+          let p = this.cacheModelEntries(model)
+            .then(model => {
+              return this.addState(model);
+            })
+            .then(model => {
+              return this.addNavItem(model);
+            })
+            .then(model => {
+              return this.getTableCfg(model);
+            })
+            .then(model => {
+              return this.getFormCfg(model);
+            })
+            .then(model => {
+              return this.storeModel(model);
+            })
+            .then(model => {
+              this.$log.debug(`[XosModelDiscovererService] Model ${model.name} stored`);
+              return this.$q.resolve();
+            })
+            .catch(err => {
+              this.$log.error(`[XosModelDiscovererService] Model ${model.name} NOT stored`);
+              // NOTE why this does not resolve?????
+              // return this.$q.resolve();
+              return this.$q.reject();
+            });
+            pArray.push(p);
+        });
+        this.$q.all(pArray)
+          .then(() => {
+            d.resolve(true);
+            this.$log.info('[XosModelDiscovererService] All models loaded!');
+          })
+          .catch(() => {
+            d.resolve(false);
+          });
+      });
+    return d.promise;
+  }
+
+  private serviceNameFromAppName(appName: string): string {
+    return appName.replace('services.', '');
+  }
+
+  private stateNameFromModel(model: IXosModel): string {
+    return `xos.${this.serviceNameFromAppName(model.app)}.${model.name.toLowerCase()}`;
+  }
+
+  private getParentStateFromModel(model: IXosModel): string {
+    let parentState: string;
+    if (model.app === 'core') {
+      parentState = 'xos.core';
+    }
+    else {
+      const serviceName = this.addService(model);
+      parentState = `xos.${serviceName}`;
+    }
+    return parentState;
+  }
+
+  private getApiUrlFromModel(model: IXosModel): string {
+    if (model.app === 'core') {
+      return `/core/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}`;
+    }
+    else {
+      const serviceName = this.serviceNameFromAppName(model.app);
+      return `/${serviceName}/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}`;
+    }
+  }
+
+  // add a service state and navigation item if it is not already there
+  private addService(model: IXosModel): string {
+    const serviceName: string = this.serviceNameFromAppName(model.app);
+    if (!_.find(this.xosServices, n => n === serviceName)) {
+      const serviceState = {
+        url: serviceName,
+        parent: 'xos',
+        abstract: true,
+        template: '<div ui-view></div>'
+      };
+      this.XosRuntimeStates.addState(`xos.${serviceName}`, serviceState);
+
+      this.XosNavigationService.add({
+        label: this.ConfigHelpers.toLabel(serviceName),
+        state: `xos.${serviceName}`,
+      });
+      this.xosServices.push(serviceName);
+    }
+    return serviceName;
+  }
+
+  private addState(model: IXosModel): ng.IPromise<IXosModel> {
+    const d = this.$q.defer();
+    const clientUrl = `/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}/:id?`;
+    const state: IXosState = {
+      parent: this.getParentStateFromModel(model),
+      url: clientUrl,
+      params: {
+        id: null
+      },
+      data: {
+        model: model.name
+      },
+      component: 'xosCrud',
+    };
+    this.XosRuntimeStates.addState(
+      this.stateNameFromModel(model),
+      state
+    );
+
+    // extend model
+    model.clientUrl = `${this.serviceNameFromAppName(model.app)}${clientUrl}`;
+
+    d.resolve(model);
+    return d.promise;
+  }
+
+  private addNavItem(model: IXosModel): ng.IPromise<IXosModel> {
+    const d = this.$q.defer();
+
+    const stateName = this.stateNameFromModel(model);
+
+    const parentState: string = this.getParentStateFromModel(model);
+
+    this.XosNavigationService.add({
+      label: this.ConfigHelpers.pluralize(model.name),
+      state: stateName,
+      parent: parentState
+    });
+
+    d.resolve(model);
+
+    return d.promise;
+  }
+
+  private cacheModelEntries(model: IXosModel): ng.IPromise<IXosModel> {
+    const d = this.$q.defer();
+
+    let called = false;
+    const apiUrl = this.getApiUrlFromModel(model);
+    this.XosModelStore.query(model.name, apiUrl)
+      .subscribe(
+        () => {
+          // skipping the first response as the observable gets created as an empty array
+          if (called) {
+            return d.resolve(model);
+          }
+          called = true;
+        },
+        err => {
+          d.reject(err);
+        }
+      );
+
+    return d.promise;
+  }
+
+  private getTableCfg(model: IXosModel): ng.IPromise<IXosModel> {
+
+    const d = this.$q.defer();
+
+    const stateUrl = this.stateNameFromModel(model);
+
+    model.tableCfg = this.ConfigHelpers.modelToTableCfg(model, stateUrl);
+
+    d.resolve(model);
+
+    return d.promise;
+  }
+
+  private getFormCfg(model: IXosModel): ng.IPromise<IXosModel> {
+
+    const d = this.$q.defer();
+
+    model.formCfg = this.ConfigHelpers.modelToFormCfg(model);
+
+    d.resolve(model);
+
+    return d.promise;
+  }
+
+  private storeModel(model: IXosModel): ng.IPromise<IXosModel> {
+
+    const d = this.$q.defer();
+
+    if (!_.find(this.xosModels, m => m.name === model.name)) {
+      this.xosModels.push(model);
+    }
+
+    d.resolve(model);
+
+    return d.promise;
+  }
+}