text: /**
   * SubjectController
   * @author      :: Massimiliano Izzo
   * @description :: Server-side logic for managing subjects
   * @help        :: See http://links.sailsjs.org/docs/controllers
   */
  /* jshint node: true */
  /* globals _, sails, DataTypePrivileges, Subject, DataType, SubjectService, OperatorService, TokenService, QueryService, DataService, DataTypeService */
  "use strict";
  
  const ControllerOut = require("xtens-utils").ControllerOut;
  const crudManager = sails.hooks.persistence.crudManager;
  const BluebirdPromise = require('bluebird');
  const ValidationError = require('xtens-utils').Errors.ValidationError;
  const PrivilegesError = require('xtens-utils').Errors.PrivilegesError;
  const NonexistentResourceError = require('xtens-utils').Errors.NonexistentResourceError;
  const xtensConf = global.sails.config.xtens;
  const DbLog = sails.hooks.dblog.log;
  const logMessages = sails.hooks.dblog.messages;
  const SUBJECT = xtensConf.constants.DataTypeClasses.SUBJECT;
  const VIEW_OVERVIEW = xtensConf.constants.DataTypePrivilegeLevels.VIEW_OVERVIEW;
  const EDIT = xtensConf.constants.DataTypePrivilegeLevels.EDIT;
  const actionUtil = require('sails/lib/hooks/blueprints/actionUtil');
  
  const coroutines = {
  
      /**
       * @method
       * @name create
       * @param{Request} req
       * @param{Response} res
       * @description coroutine for new Subject instance creation
       */
      create: BluebirdPromise.coroutine(function * (req, res) {
          let subject = req.allParams();
          const operator = TokenService.getToken(req);
          const dataTypePrivilege = yield DataTypeService.getDataTypePrivilegeLevel(operator.groups, subject.type);
          if (!dataTypePrivilege || _.isEmpty(dataTypePrivilege || dataTypePrivilege.privilegeLevel != EDIT)) {
              throw new PrivilegesError(`Authenticated user has not edit privileges on the subject type ${subject.type}`);
          }
          SubjectService.simplify(subject);
          const dataType = yield DataType.findOne(subject.type);
  
          const validationRes = yield SubjectService.validate(subject, true, dataType);
          if (validationRes.error !== null) {
              throw new ValidationError(validationRes.error);
          }
          subject = validationRes.value;
          const dataTypeName = dataType && dataType.name;
          const result = yield crudManager.createSubject(subject, dataTypeName);
  
          DbLog(logMessages.CREATE, SUBJECT, result.id, result.owner, result.type, operator.id);
          sails.log.info(result);
          res.set('Location', `${req.baseUrl}${req.url}/${result.id}`);
          return res.json(201, result);
      }),
  
      findOne: BluebirdPromise.coroutine(function * (req, res) {
          const id = req.param('id');
          const operator = TokenService.getToken(req);
  
          let query = Subject.findOne(id);
          query = actionUtil.populateRequest(query, req);
  
          if (operator.canAccessPersonalData) {
              query.populate('personalInfo');
          }
  
          let subject = yield BluebirdPromise.resolve(query);
          const idSubjectType = subject ? _.isObject(subject.type) ? subject.type.id : subject.type : undefined;
          const dataTypePrivilege = yield DataTypeService.getDataTypePrivilegeLevel(operator.groups, idSubjectType);
  
          // filter Out Metadata if operator has not the privilege
          if (!dataTypePrivilege || _.isEmpty(dataTypePrivilege)) { subject = {}; } else if (dataTypePrivilege.privilegeLevel === VIEW_OVERVIEW) { subject.metadata = {}; }
  
          if (!operator.canAccessSensitiveData && !_.isEmpty(subject.metadata)) {
              subject = yield DataService.filterOutSensitiveInfo(subject, operator.canAccessSensitiveData);
          }
  
          DbLog(logMessages.FINDONE, SUBJECT, id, subject.owner, idSubjectType, operator.id);
          return res.json(subject);
      }),
  
      find: BluebirdPromise.coroutine(function * (req, res) {
          const operator = TokenService.getToken(req);
          let allPrivileges = yield DataTypePrivileges.find({ group: operator.groups });
          allPrivileges = operator.groups.length > 1 ? DataTypeService.getHigherPrivileges(allPrivileges) : allPrivileges;
  
          let params = req.allParams();
          params.model = SUBJECT;
          params.privilegeLevel = VIEW_OVERVIEW;
          params.idOperator = operator.id;
          if (operator.canAccessPersonalData) {
              params.canAccessPersonalData = true;
          }
  
          let subjects = yield crudManager.findData(params);
          // let subjects = yield BluebirdPromise.resolve(query);
          const dataTypesId = !_.isEmpty(subjects) ? _.isObject(subjects[0].type) ? _.uniq(_.map(_.map(subjects, 'type'), 'id')) : _.uniq(_.map(subjects, 'type')) : [];
          const pagePrivileges = allPrivileges.filter(obj => {
              return _.find(dataTypesId, id => { return id === obj.dataType; });
          });
  
          const [payload, headerInfo] = yield BluebirdPromise.all([
              DataService.filterListByPrivileges(subjects, dataTypesId, pagePrivileges, operator.canAccessSensitiveData),
              QueryService.composeHeaderInfo(req, params)
          ]);
  
          // NOTE: Log or not Log this is question
          // DbLog(logMessages.FIND, SUBJECT, subjects.length, _.uniq(_.map(subjects, 'owner')), dataTypesId, operator.id);
          return DataService.prepareAndSendResponse(res, payload, headerInfo);
      }),
  
      update: BluebirdPromise.coroutine(function * (req, res) {
          let subject = req.allParams();
          const operator = TokenService.getToken(req);
  
          let result = yield DataService.hasDataSensitive(subject.id, SUBJECT);
          if (result.hasDataSensitive && !operator.canAccessSensitiveData) {
              throw new PrivilegesError("Authenticated user is not allowed to modify sensitive data");
          }
  
          const idSubjectType = _.isObject(subject.type) ? subject.type.id : subject.type;
          const dataTypePrivilege = yield DataTypeService.getDataTypePrivilegeLevel(operator.groups, idSubjectType);
          if (!dataTypePrivilege || dataTypePrivilege.privilegeLevel != EDIT) {
              throw new PrivilegesError(`Authenticated user has not edit privileges on the subject type ${subject.type}`);
          }
  
          let query = Subject.findOne(subject.id);
          query = actionUtil.populateRequest(query, req);
          let prevSubject = yield BluebirdPromise.resolve(query);
  
          SubjectService.simplify(subject);
  
          const dataType = yield DataType.findOne(idSubjectType);
          const validationRes = yield SubjectService.validate(subject, true, dataType);
          if (validationRes.error !== null) {
              throw new ValidationError(validationRes.error);
          }
          const dataTypeName = dataType && dataType.name;
          subject = validationRes.value;
          const updatedSubject = yield crudManager.updateSubject(subject, dataTypeName);
  
          let qUpdate = Subject.findOne(subject.id);
          qUpdate = actionUtil.populateRequest(qUpdate, req);
          let upSubject = yield BluebirdPromise.resolve(qUpdate);
  
          DbLog(logMessages.UPDATE, SUBJECT, updatedSubject.id, updatedSubject.owner, updatedSubject.type, operator.id, { prevData: prevSubject, upData: upSubject });
          return res.json(updatedSubject);
      }),
  
      destroy: BluebirdPromise.coroutine(function * (req, res, co) {
          const id = req.param('id');
          const operator = TokenService.getToken(req);
  
          if (!id) {
              return co.badRequest({ message: 'Missing subject ID on DELETE request' });
          }
  
          const subject = yield Subject.findOne({ id: id });
          if (!subject) {
              throw new NonexistentResourceError("Missing Resource");
          }
          // retrieve dataType id
          const idSubjectType = _.isObject(subject.type) ? subject.type.id : subject.type;
  
          const dataTypePrivilege = yield DataTypeService.getDataTypePrivilegeLevel(operator.groups, idSubjectType);
          if (!dataTypePrivilege || dataTypePrivilege.privilegeLevel != EDIT) {
              throw new PrivilegesError(`Authenticated user has not edit privileges on the subject type ${subject.type}`);
          }
          sails.log.info(`Subject to be deleted:  ${subject.id}`);
  
          const deleted = yield crudManager.deleteSubject(id);
          if (deleted > 0) {
              DbLog(logMessages.DELETE, SUBJECT, subject.id, subject.owner, subject.type, operator.id, { deletedData: JSON.stringify(subject) });
          }
          return res.json(200, { deleted: deleted });
      }),
  
      edit: BluebirdPromise.coroutine(function * (req, res) {
          const operator = TokenService.getToken(req);
          const params = req.allParams();
          const id = req.param("id");
          const code = req.param("code");
          const project = req.param("project");
          sails.log.info("SubjectController.edit - Decoded ID is: " + operator.id);
  
          const payload = yield BluebirdPromise.props({
              subject: SubjectService.getOneAsync(id, code),
              dataTypes: crudManager.getDataTypesByRolePrivileges({
                  idOperator: operator.id,
                  idDataTypes: params.idDataTypes,
                  parentDataType: params.parentDataType,
                  model: SUBJECT,
                  project: project,
                  privilegeLevel: EDIT
              }),
              parentSubject: SubjectService.getOneAsync(params.parentSubject, params.parentSubjectCode)
          });
  
          if (_.isEmpty(payload.dataTypes)) { throw new PrivilegesError(`Authenticated user has not edit privileges on any subject type`); }
  
          if (payload.subject) {
              let operators = yield OperatorService.getOwners(payload.subject);
              payload.operators = operators;
              // if operator has not access to Sensitive Data and dataType has sensitive data, then return forbidden
              const sensitiveRes = yield DataService.hasDataSensitive(payload.subject.id, SUBJECT);
              if (sensitiveRes && ((sensitiveRes.hasDataSensitive && !operator.canAccessSensitiveData))) {
                  throw new PrivilegesError("Authenticated user is not allowed to edit sensitive data");
                  // if edit subject exists and operator has not the privilege to EDIT datatype, then throw Privileges Error
              }
              if (_.isEmpty(payload.dataTypes) || !_.find(payload.dataTypes, { id: payload.subject.type.id })) {
                  throw new PrivilegesError(`Authenticated user has not edit privileges on the subject type`);
              }
          }
          return res.json(payload);
      }),
  
      getNextSubjectCode: BluebirdPromise.coroutine(function * (req, res) {
          const operator = TokenService.getToken(req);
          const subject = req.allParams();
          sails.log.info("SubjectController.getNextSubjectCode - Decoded ID is: " + operator.id);
          if (!subject.type) { throw new Error(`Error getting last subject code - Please provide a data type`); }
  
          const nextCode = yield crudManager.getNextSubjectCode(subject);
  
          return res.json(nextCode);
      }),
  
      createGraph: BluebirdPromise.coroutine(function * (req, res, co) {
          let idSubject = req.param("idPatient");
          const subjectCode = req.param("codePatient");
  
          if ((!idSubject || isNaN(idSubject)) && subjectCode) {
              let subj = yield SubjectService.getOneAsync(null, subjectCode);
              if (!subj) {
                  throw new NonexistentResourceError(`No subject found with code: ${subjectCode}`);
              }
              idSubject = subj.id;
          } else if ((!idSubject || isNaN(idSubject)) && !subjectCode) {
              throw new NonexistentResourceError("No valid idSubject or CodeSubject provided");
          }
          const fetchSubjectDataTree = sails.hooks['persistence'].getDatabaseManager().recursiveQueries.fetchSubjectDataTree;
          const operator = TokenService.getToken(req);
          let dataTypePrivileges;
  
          return DataTypePrivileges.find({ group: operator.groups }).populate('dataType')
              .then(results => {
                  dataTypePrivileges = results;
                  // operator has not privileges on datatype, then throw Privileges Error
                  if (_.isEmpty(dataTypePrivileges)) {
                      throw new PrivilegesError(`Authenticated user has not privileges`);
                  }
                  return fetchSubjectDataTree(idSubject, subjectTreeCb.bind({ idSubject, co }));
              })
              .catch(err => {
                  sails.log.error(err);
                  return co.error(err);
              });
  
          function subjectTreeCb (err, resp) {
              /* istanbul ignore if */
              if (err) {
                  sails.log.error(err);
                  return co.error(err);
              } else {
                  let links = []; let idRows = [];
                  _.forEach(resp.rows, function (row) {
                      _.find(dataTypePrivileges, function (d) {
                          if (d.dataType.name === row.type) {
                              idRows.push(row.id);
                          }
                      });
                  });
                  BluebirdPromise.map(resp.rows, function (row) {
                      let privilege, pedit, pdown, pdet, pover;
                      var privileges = _.filter(dataTypePrivileges, function (d) {
                          return d.dataType.name === row.type;
                      });
  
                      pedit = _.find(privileges, function (d) {
                          return d.privilegeLevel === 'edit';
                      });
                      pdown = _.find(privileges, function (d) {
                          return d.privilegeLevel === 'download';
                      });
                      pdet = _.find(privileges, function (d) {
                          return d.privilegeLevel === 'view_details';
                      });
                      pover = _.find(privileges, function (d) {
                          return d.privilegeLevel === 'view_overview';
                      });
                      privilege = pedit || pdown || pdet || pover || undefined;
  
                      if (privilege) {
                          if (privilege.privilegeLevel === VIEW_OVERVIEW) { row.metadata = {}; }
                          if (row.parent_data !== null && _.find(idRows, function (i) { return i === row.parent_data; })) {
                              return { 'source': row.parent_data, 'target': row.id, 'name': row.id, 'type': row.type, 'typeId': row.typeId, 'biobankCode': row.biobankcode, 'metadata': row.metadata, privilege: privilege.privilegeLevel };
                          } else if (row.parent_sample !== null && _.find(idRows, function (i) { return i === row.parent_sample; })) {
                              return { 'source': row.parent_sample, 'target': row.id, 'name': row.id, 'type': row.type, 'typeId': row.typeId, 'biobankCode': row.biobankcode, 'metadata': row.metadata, privilege: privilege.privilegeLevel };
                          } else if (row.parent_subject !== null && _.find(idRows, function (i) { return i === row.parent_subject; })) {
                              return { 'source': row.parent_subject, 'target': row.id, 'name': row.id, 'type': row.type, 'typeId': row.typeId, 'biobankCode': row.biobankcode, 'metadata': row.metadata, privilege: privilege.privilegeLevel };
                          } else {
                              return { 'source': 'Patient', 'target': row.id, 'name': row.id, 'type': row.type, 'typeId': row.typeId, 'biobankCode': row.biobankcode, 'metadata': row.metadata, privilege: privilege.privilegeLevel };
                          }
                      }
                  })
                      .then(function (link) {
                          links = _.compact(link);
                          links = links && links.length > 0 ? links : [{ 'source': 'Patient', 'target': null, 'name': idSubject, 'type': 'Patient', 'metadata': {} }];
                          let json = {
                              'links': links,
                              'idPatient': idSubject
                          };
                          return res.json(json);
                      })
                      .catch(/* istanbul ignore next */ function (err) {
                          sails.log(err);
                          return co.error(err);
                      });
              }
          }
      })
  
  };
  
  module.exports = {
  
      /**
       *  POST /subject
       *  @method
       *  @name create
       *  @description:  create a new subject; transaction-safe implementation
       */
      create: function (req, res) {
          const co = new ControllerOut(res);
          coroutines.create(req, res)
              .catch(error => {
                  sails.log.error("SubjectController.create: " + error.message);
                  return co.error(error);
              });
      },
  
      /**
       * GET /subject/:id
       * @method
       * @name findOne
       * @description - retrieve an existing subject
       */
      findOne: function (req, res) {
          const co = new ControllerOut(res);
          coroutines.findOne(req, res)
              .catch(error => {
                  /* istanbul ignore next */
                  return co.error(error);
              });
      },
  
      /**
       * GET /subject
       * GET /subject/find
       *
       * @method
       * @name find
       * @description Find samples based on criteria provided in the request
       */
      find: function (req, res) {
          const co = new ControllerOut(res);
          coroutines.find(req, res)
              .catch(err => {
                  sails.log.error(err);
                  return co.error(err);
              });
      },
  
      /**
       * PUT /subject/:id
       * @method
       * @name update
       * @description - update an existing subject.
       *              Transaction-safe implementation
       */
      update: function (req, res) {
          const co = new ControllerOut(res);
          coroutines.update(req, res)
              .catch(error => {
                  sails.log.error(error);
                  return co.error(error);
              });
      },
  
      /**
       * DELETE /subject/:id
       * @method
       * @name destroy
       * @description
       */
      destroy: function (req, res) {
          const co = new ControllerOut(res);
          coroutines.destroy(req, res, co)
              .catch(err => {
                  if (err instanceof NonexistentResourceError) {
                      return res.json(200, { deleted: 0 });
                  }
                  sails.log.error(err);
                  return co.error(err);
              });
      },
  
      /**
       * @method
       * @name edit
       * @description retrieve all required models for editing/creating a Subject via client web-form
       */
      edit: function (req, res) {
          const co = new ControllerOut(res);
          coroutines.edit(req, res)
              .catch(err => {
                  /* istanbul ignore next */
                  sails.log.error(err);
                  return co.error(err);
              });
      },
  
      /**
      * @method
      * @name getNextSubjectCode
      * @description retrieve last biobank code for creating a Sample via client web-form
      */
      getNextSubjectCode: function (req, res) {
          const co = new ControllerOut(res);
          coroutines.getNextSubjectCode(req, res)
              .catch(err => {
                  sails.log.error(err);
                  return co.error(err);
              });
      },
  
      /**
       * @method
       * @name createGraph
       * @description generate and visualize the (nested/multi level) data graph for a given subject.
       *              Note: The current limit for the number of instances is 100.
       */
      createGraph: function (req, res) {
          const co = new ControllerOut(res);
          coroutines.createGraph(req, res)
              .catch(err => {
                  sails.log.error(err);
                  return co.error(err);
              });
      },
  
      /**
       * @method
       * @name createGraphSimple
       * @description generate and visualize the patient's data graph. All the descendant data are shown as children.
       *              Only one data instance per datatype is shown.
       * @deprecated
       *
       */
      createGraphSimple: function (req, res) {
          const co = new ControllerOut(res);
          const fetchSubjectDataTreeSimple = sails.hooks['persistence'].getDatabaseManager().recursiveQueries.fetchSubjectDataTreeSimple;
          const idSubject = req.param("idPatient");
          const operator = TokenService.getToken(req);
          let dataTypePrivileges;
  
          return DataTypePrivileges.find({ group: operator.groups }).populate('dataType')
              .then(results => {
                  dataTypePrivileges = results;
                  // operator has not the privilege to EDIT datatype, then throw Privileges Error
                  if (_.isEmpty(dataTypePrivileges)) {
                      throw new PrivilegesError(`Authenticated user has not privileges`);
                  }
                  return fetchSubjectDataTreeSimple(idSubject, subjectTreeSimpleCb);
              })
              .catch(err => {
                  sails.log.error(err);
                  return co.error(err);
              });
  
          function subjectTreeSimpleCb (err, resp) {
              let children = []; let links = [];
  
              sails.log(resp);
              /* istanbul ignore if */
              if (resp.rows.length === 0) {
                  links = [{
                      'source': 'Patient',
                      'target': null
                  }];
  
                  return res.json({ links: links });
              }
  
              for (let i = 0; i < resp.rows.length; i++) {
                  children.push(resp.rows[i].id);
              }
  
              sails.log(children);
  
              BluebirdPromise.map(children, function (child) {
                  let privilege, childName;
  
                  return DataType.findOne(child).then(function (dataType) {
                      childName = dataType.name;
                      if (_.find(dataTypePrivileges, function (d) {
                          privilege = d;
                          return privilege.dataType.name === childName;
                      })) {
                          return { 'source': 'Patient', 'target': childName };
                      }
                  });
              })
                  .then(function (link) {
                      links = _.reject(link, function (l) { return l === undefined; });
                      let json = { 'links': links };
                      return res.json(json);
                  })
                  .catch(/* istanbul ignore next */ function (err) {
                      sails.log.error(err);
                      return co.error(err);
                  });
          }
      }
  
  };
  