whole_func_string: function odagMixin(options, params) {
  
          var qrsApi = qrs(options);
          
          var global = this;
          var task = new Task();
          
          var templateApp = params.templateApp;
  
          if (typeof templateApp == 'undefined') {
              return promise.reject('Please provide the templateApp parameter');
          }
  
          var maxParDup = undef.if(params.maxParDup, 1);
  
          // Handles parameters default values
  
          var defaultReplaceApp = '%(targetAppName)s';
          var defaultTargetApp = '%(templateAppName)s %(replaceValue)s';
          var defaultCreateReloadTask = {
              task: {
                  name: 'Reload for %(targetAppName)s',
                  taskType: 0,
                  enabled: true,
                  taskSessionTimeout: 1440,
                  maxRetries: 0,
                  tags: [],
                  app: {},
                  isManuallyTriggered: false,
                  customProperties: []
              },
              compositeEvents: [],
              schemaEvents: [{
                  name: 'Hourly',
                  enabled: true,
                  eventType: 0,
                  startDate: '2016-01-01T00:00:00.000Z',
                  expirationDate: '9999-12-30T23:00:00.000Z',
                  schemaFilterDescription: ['* * - * * * * *'],
                  incrementDescription: '0 1 0 0',
                  incrementOption: '1',
                  privileges: ['read','update','create','delete']
              }]
          };
  
          var replacesDef = undef.if(params.replacesDef, false);
          var duplicateApp = undef.if(params.duplicateApp, true);
          var reloadApp = undef.if(params.reloadApp, true);
          var keepApp = undef.if(params.keepApp, true);
          var overwriteApp = undef.if(params.overwriteApp, true);
          var replaceFullRow = undef.if(params.replaceFullRow, false);
          var reloadRegex = undef.if(params.reloadRegex, /(.*)/);
          var replaceApp = undef.if(params.replaceApp, defaultReplaceApp);
          var targetApp = undef.if(params.targetApp, defaultTargetApp);
          var publishStream = undef.if(params.publishStream, false);
          var createReloadTask = undef.if(params.createReloadTask, false);
          var customProperties = undef.if(params.customProperties, false);
  
          // Convert simplified syntax to advanced syntax
  
          if (replacesDef && !Array.isArray(replacesDef)) {
              replacesDef = [replacesDef];
          }
  
          replacesDef = replacesDef.map(function(item) {
              var retVal = {};
  
              if (typeof item.replaces === 'undefined' && typeof item.marker !== 'undefined' && typeof item.value !== 'undefined') {
                  var newReplace = {marker: item.marker, value: item.value};
                  if (typeof item.fullRow !== 'undefined') newReplace.fullRow = item.fullRow;
                  retVal.replaces = [newReplace];
              } else if (typeof item.replaces === 'undefined' && typeof item.marker === 'undefined' && typeof item.value === 'undefined') {
                  retVal.replaces = false;
              } else if (typeof item.replaces !== 'undefined' && !Array.isArray(item.replaces)) {
                  retVal.replaces = [item.replaces];
              } else if (typeof item.replaces !== 'undefined' && Array.isArray(item.replaces)) {
                  retVal.replaces = item.replaces;
              } else {
                  task.running('warning', 'Unhandled replace parameter: %s', item);
                  return undefined;
              }
  
              retVal.replaces.forEach(function(replace) {
                  if (!Array.isArray(replace.value))
                      replace.value = [replace.value];
              });
  
              retVal.duplicateApp = undef.if(item.duplicateApp, duplicateApp);
              retVal.reloadApp = undef.if(item.reloadApp, reloadApp);
              retVal.keepApp = undef.if(item.keepApp, keepApp);
              retVal.overwriteApp = undef.if(item.overwriteApp, overwriteApp);
              retVal.replaceApp = undef.if(item.replaceApp, replaceApp);
              retVal.targetApp = undef.if(item.targetApp, targetApp);
              retVal.publishStream = undef.if(item.publishStream, publishStream);
              retVal.createReloadTask = undef.if(item.createReloadTask, createReloadTask);
              retVal.customProperties = undef.if(item.customProperties, customProperties);
  
              if (retVal.replaceApp == true) {
                  retVal.replaceApp = defaultReplaceApp;
              }
  
              if (retVal.targetApp == true) {
                  retVal.targetApp = defaultTargetApp;
              }
  
              if (retVal.createReloadTask == true) {
                  retVal.createReloadTask = defaultCreateReloadTask;
              }
  
              if (retVal.customProperties && !Array.isArray(retVal.customProperties)) {
                  retVal.customProperties = [retVal.customProperties];
              }
  
              if (retVal.customProperties) {
                  retVal.customProperties.forEach(function(customProperty) {
                      if (customProperty.values && !Array.isArray(customProperty.values)) {
                          customProperty.values = [customProperty.values];
                      }
                  });
              }
  
              return retVal;
          }).filter(function(item) {
              return typeof item !== 'undefined';
          });
  
          replacesDef = replacesDef.map(function(item) {
              return item.replaces.map(function(replace) {
                  return replace.value.map(function(val) {
                      return {marker: replace.marker, value: val, fullRow: ((typeof replace.fullRow !== 'undefined') ? replace.fullRow : false)};
                  });
              }).reduce(function(pv, cv) {
                  var retVal = [];
                  cv.forEach(function(cvitem) {
                      pv.forEach(function(pvitem) {
                          retVal.push(pvitem.concat([cvitem]));
                      });
                  });
  
                  return retVal;
              }, [[]]).map(function(replace) {
                  return {
                      replace: replace,
                      duplicateApp: item.duplicateApp,
                      reloadApp: item.reloadApp,
                      keepApp: item.keepApp,
                      overwriteApp: item.overwriteApp,
                      replaceApp: item.replaceApp,
                      targetApp: item.targetApp,
                      publishStream: item.publishStream,
                      createReloadTask: (item.createReloadTask) ? extend(true, {}, item.createReloadTask) : false,
                      customProperties: (item.customProperties) ? extend(true, [], item.customProperties) : false
                  };
              });
          }).reduce(function(pv, cv) {
              return pv.concat(cv);
          });
  
          // If no clone task was provided, just return an empty array
  
          if (replacesDef.length == 0) {
              task.done([]);
          } else {
  
              // Else start the cloning process
  
              promise().then(function() {
  
                  // Start
  
                  task.running('info', 'Starting!');
  
              }).then(function(reply) {
  
                  // Connected
  
                  return promise.all([
                      global.getDocList().then(function(reply) {
                          task.running('info', 'Got document List');
                          return reply;
                      }),
                      global.getStreamList().then(function(reply) {
                          task.running('info', 'Got Stream List');
                          return reply;
                      }),
                      qrsApi.custompropertydefinition.get()
                  ]);
  
              }).then(function([docList, streamList, cpList]) {
  
                  // Replace stream & app names by ids
  
                  // Looping on replace definition
  
                  var pendingActions = [];
                  var missingCpValues = {};
                  var templateFound;
                  replacesDef.forEach(function(replaceDef) {
  
                      //<editor-fold desc="Make sure the publish stream is an id and not a name">
  
                      if (replaceDef.publishStream && !replaceDef.publishStream.match(/^[a-f0-9\-]{36}$/)) {
  
                          task.running('info', 'Stream to publish into is an stream name (%s), trying to find the associated id', replaceDef.publishStream);
  
                          var arrayStreamFound = streamList.filter(function(item) {
                              return (item.qName == replaceDef.publishStream);
                          });
  
                          if (arrayStreamFound.length > 1) {
                              task.running('warning', 'Several streams found : %s, using first one (%s)', arrayStreamFound.map(function(app) {
                                  return app.qId;
                              }), arrayStreamFound[0].qId);
                          } else if (arrayStreamFound.length == 0) {
                              pendingActions.push(promise.reject('Stream not found: ' + replaceDef.publishStream));
                              return;
                          } else {
                              task.running('info', 'Stream found');
                          }
  
                          replaceDef.publishStream = arrayStreamFound[0].qId;
  
                      } else if (replaceDef.publishStream) {
  
                          task.running('info', 'Stream to publish into is an id (%s), checking if it exists', replaceDef.publishStream);
  
                          var arrayStreamFound = streamList.filter(function(item) {
                              return (item.qId == replaceDef.publishStream);
                          });
  
                          if (arrayStreamFound.length == 0) {
                              pendingActions.push(promise.reject('No :( Stream not found: ' + replaceDef.publishStream));
                              return;
                          } else {
                              task.running('info', 'Yes! Stream found');
                          }
  
                      }
  
                      //</editor-fold>
  
                      //<editor-fold desc="Make sure the template app is an id and not a name">
  
                      var arrayTemplateFound = docList.filter(function(item) {
                          return (templateApp.match(/^[a-f0-9\-]{36}$/) && item.qDocId == templateApp) ||
                              (!templateApp.match(/^[a-f0-9\-]{36}$/) && item.qDocName == templateApp);
                      });
  
                      if (arrayTemplateFound.length > 1) {
                          task.running('warning', 'Several apps found : %s, using first one (%s)', arrayTemplateFound.map(function(app) {
                              return app.qDocId;
                          }), arrayTemplateFound[0].qDocId);
                      } else if (arrayTemplateFound.length == 0) {
                          pendingActions.push(promise.reject('Template not found: ' + templateApp));
                          return;
                      } else {
                          task.running('info', 'Template found');
                      }
  
                      templateFound = arrayTemplateFound[0];
  
                      //</editor-fold>
  
                      //<editor-fold desc="Replace sprintf values">
  
                      var replaceDic = docList.map(function(doc) {
                          return {key: doc.qDocId, value: doc.qDocName};
                      }).reduce(function(pv, cv) {
                          pv[cv.key] = cv.value;
                          return pv;
                      }, {});
  
                      var replaceValue = replaceDef.replace.map(function(item) {
                          return item.value;
                      }).reduce(function(pv, cv) {
                          return sprintf('%s %s', pv, cv);
                      });
  
                      if (replaceDef.targetApp)
                          replaceDef.targetApp = sprintf(replaceDef.targetApp, {templateAppId: arrayTemplateFound[0].qDocId, templateAppName: replaceDic[arrayTemplateFound[0].qDocId], replaceValue: replaceValue, replace: replaceDef.replace});
  
                      if (replaceDef.replaceApp)
                          replaceDef.replaceApp = sprintf(replaceDef.replaceApp, {templateAppId: arrayTemplateFound[0].qDocId, templateAppName: replaceDic[arrayTemplateFound[0].qDocId], replaceValue: replaceValue, replace: replaceDef.replace, targetAppName: replaceDef.targetApp});
  
                      if (replaceDef.createReloadTask && replaceDef.createReloadTask.task && replaceDef.createReloadTask.task.name)
                          replaceDef.createReloadTask.task.name = sprintf(replaceDef.createReloadTask.task.name, {templateAppId: arrayTemplateFound[0].qDocId, templateAppName: replaceDic[arrayTemplateFound[0].qDocId], replaceValue: replaceValue, replace: replaceDef.replace, targetAppName: replaceDef.targetApp});
  
                      if (replaceDef.customProperties)
                          replaceDef.customProperties.forEach(function(customProperty) {
                              if (customProperty.values) {
                                  customProperty.values = customProperty.values.map(function(value) {
                                      return sprintf(value, {templateAppId: arrayTemplateFound[0].qDocId, templateAppName: replaceDic[arrayTemplateFound[0].qDocId], replaceValue: replaceValue, replace: replaceDef.replace, targetAppName: replaceDef.targetApp});
                                  });
                              }
                          });
  
                      //</editor-fold>
  
                      //<editor-fold desc="Make sure the replace apps are ids and not names">
  
                      if (replaceDef.replaceApp && !replaceDef.replaceApp.match(/^[a-f0-9\-]{36}$/)) {
  
                          task.running('info', 'App to replace is an app name (%s), trying to find the associated id', replaceDef.replaceApp);
  
                          var arrayAppFound = docList.filter(function(item) {
                              return (
                                  item.qDocName == replaceDef.replaceApp &&
                                  (
                                      (replaceDef.publishStream && item.qMeta.stream && item.qMeta.stream.id == replaceDef.publishStream) ||
                                      (!replaceDef.publishStream && !item.published)
                                  )
                              );
                          });
  
                          if (arrayAppFound.length > 1) {
                              task.running('warning', 'Several apps found : %s, using first one (%s)', arrayAppFound.map(function(app) {
                                  return app.qDocId;
                              }), arrayAppFound[0].qDocId);
                              replaceDef.replaceApp = arrayAppFound[0].qDocId;
                          } else if (arrayAppFound.length == 0) {
                              task.running('warning', 'App not found (%s)', replaceDef.replaceApp);
                              replaceDef.replaceApp = false;
                          } else {
                              task.running('info', 'App found');
                              replaceDef.replaceApp = arrayAppFound[0].qDocId;
                          }
  
                      } else if (replaceDef.replaceApp) {
  
                          task.running('info', 'App to replace is an id (%s), checking if it exists', replaceDef.replaceApp);
  
                          var arrayAppFound = docList.filter(function(item) {
                              return (item.qDocId == replaceDef.replaceApp && item.qMeta.stream && item.qMeta.stream.id == replaceDef.publishStream);
                          });
  
                          if (arrayAppFound.length == 0) {
                              task.running('warning', 'App not found (%s)', replaceDef.replaceApp);
                              replaceDef.replaceApp = false;
                          } else {
                              task.running('info', 'App found');
                          }
  
                      }
  
                      //</editor-fold>
  
                      //<editor-fold desc="Make sure custom properties are ids and that values exist">
  
                      if (replaceDef.customProperties) {
  
                          replaceDef.customProperties.forEach(function(customProperty) {
  
                              var arrayCpFound = cpList.filter(function(item) {
                                  return (item.name == customProperty.name && !customProperty.name.match(/^[a-f0-9\-]{36}$/)) ||
                                      (item.id == customProperty.name && customProperty.name.match(/^[a-f0-9\-]{36}$/));
                              });
  
                              if (arrayCpFound.length > 1) {
                                  task.running('warning', 'Several matching custom properties found : %s, using first one (%s)', arrayCpFound.map(function(cp) {
                                      return cp.id;
                                  }), arrayCpFound[0].id);
                              } else if (arrayCpFound.length == 0) {
                                  pendingActions.push(promise.reject('Custom property not found: ' + customProperty.name));
                                  return;
                              } else {
                                  task.running('info', 'Custom property found');
                              }
  
                              customProperty.name = arrayCpFound[0].id;
  
                              if (typeof missingCpValues[arrayCpFound[0].id] === 'undefined') {
                                  missingCpValues[arrayCpFound[0].id] = [];
                              }
  
                              var missingValues = missingCpValues[arrayCpFound[0].id];
                              customProperty.values.forEach(function(value) {
                                  if (arrayCpFound[0].choiceValues.indexOf(value) < 0 && missingValues.indexOf(value) < 0) {
                                      missingValues.push(value);
                                  }
                              });
                          });
  
                      }
  
                      //</editor-fold>
  
                  });
  
                  Object.keys(missingCpValues).forEach(function(cpkey) {
                      var missingValues = missingCpValues[cpkey];
  
                      var addToCp = qrsApi.custompropertydefinition.id(cpkey).get().then(function(cp) {
  
                          if (cp.objectTypes.indexOf('App') < 0) {
                              return promise.reject('Custom property is not applicable to Apps...');
                          }
  
                          if (missingValues.length > 0) {
                              task.running('warning', 'Adding missing values in custom property: %s', missingValues);
                              cp.choiceValues = cp.choiceValues.concat(missingValues);
                              return qrsApi.custompropertydefinition.id(cpkey).put(cp);
                          }
  
                      });
  
                      pendingActions.push(addToCp);
                  });
  
                  return promise.all(pendingActions).then(function() {
                      return promise.all([
                          templateFound.qDocId,
                          templateFound.qDocName
                      ]);
                  });
  
              }).then(function([templateId, templateName]) {
  
                  // Clone app
  
                  var newAppsStep = promise.resolve([]);
                  var newApps = [];
                  arrayDivide(replacesDef, maxParDup).forEach(function(scriptReplacesChunk) {
                      var newAppsChunk = [];
  
                      scriptReplacesChunk.forEach(function(scriptReplace) {
                          newAppsChunk.push(
                              function() {
  
                                  if (!scriptReplace.duplicateApp) {
  
                                      task.running('warning', 'Working directly on template app (%s)!', templateId);
  
                                      return qrsApi.app.id(templateId).get().then(function(reply) {
                                          scriptReplace.publishStream = (reply.published ? false : scriptReplace.publishStream);
                                          scriptReplace.replaceApp = false;
  
                                          return {
                                              scriptReplace: scriptReplace,
                                              clonedApp: reply
                                          };
                                      });
  
                                  } else if (scriptReplace.replaceApp && !scriptReplace.overwriteApp) {
  
                                      task.running('info', 'App already exists! Working on existing app (%s)', scriptReplace.replaceApp);
  
                                      return qrsApi.app.id(scriptReplace.replaceApp).get().then(function(reply) {
                                          scriptReplace.publishStream = (reply.published ? false : scriptReplace.publishStream);
                                          scriptReplace.replaceApp = false;
  
                                          return {
                                              scriptReplace: scriptReplace,
                                              clonedApp: reply
                                          };
                                      });
  
                                  } else {
  
                                      var targetAppName = scriptReplace.targetApp;
                                      task.running('info', 'Generating clone named \'%s\'', targetAppName);
  
                                      return qrsApi.app.id(templateId).copy.post(undefined, {name: targetAppName}).then(function(clonedApp) {
                                          task.running('info', 'Clone named \'%s\' created successfully with id: %s', targetAppName, clonedApp.id);
                                          return {
                                              scriptReplace: scriptReplace,
                                              clonedApp: clonedApp
                                          };
                                      });
                                  }
                              }
                          );
                      });
  
                      newAppsStep = newAppsStep.then(function(reply) {
                          newApps = newApps.concat(reply);
                          return promise.all(newAppsChunk.map(function(promiseFactory) {
                              return promiseFactory();
                          }));
                      });
  
                  });
  
                  return newAppsStep.then(function(reply) {
                      return newApps.concat(reply);
                  });
  
              }).then(function(newApps) {
  
                  // Perform modifications on clone
  
                  // Replace script marker and reload in //
                  // Only if marker is not null!
  
                  var newAppsScriptReplacedStep = promise.resolve([]);
                  var newAppsScriptReplaced = [];
                  arrayDivide(newApps, maxParDup).forEach(function(newAppsChunk) {
  
                      var newAppsScriptReplacedChunk = [];
                      newAppsChunk.forEach(function(newApp) {
  
                          newAppsScriptReplacedChunk.push(
                              function() {
  
                                  var step = promise();
  
                                  // If nothing to do just return
  
                                  if (!(newApp.scriptReplace.replace || newApp.scriptReplace.reloadApp || newApp.scriptReplace.publishStream)) return promise.resolve([]);
  
                                  // If anything to do, open socket
  
                                  if (newApp.scriptReplace.replace || newApp.scriptReplace.reloadApp) {
  
                                      step = step.then(function() {
  
                                          return global.openDoc(newApp.clonedApp.id).then(function(reply) {
                                              task.running('info', {id: newApp.clonedApp.id, msg: 'Doc opened'});
                                              return reply;
                                          })
  
                                      });
  
                                  }
  
                                  // If a replace is required, do it!
  
                                  if (newApp.scriptReplace.replace) {
  
                                      step = step.then(function(clonedApp) {
  
                                          // Replace the script marker by the replace value
  
                                          task.running('info', {id: newApp.clonedApp.id, msg: 'Application opened'});
  
                                          return promise().then(function() {
                                              return clonedApp.getScript();
                                          }).then(function(result) {
                                              task.running('info', {id: newApp.clonedApp.id, msg: 'Application script extracted'});
  
                                              var newScript = result;
                                              newApp.scriptReplace.replace.forEach(function(replace) {
                                                  if (replace.fullRow) {
                                                      var re = new RegExp(sprintf('^.*%s.*$', replace.marker), 'm');
                                                      newScript = newScript.replace(re, sprintf('%s // %s', replace.value, replace.marker));
                                                  } else {
                                                      newScript = newScript.replace(replace.marker, replace.value);
                                                  }
                                              });
  
                                              return clonedApp.setScript(newScript);
                                          }).then(function() {
                                              task.running('info', {id: newApp.clonedApp.id, msg: 'Application script replaced'});
                                              return clonedApp.doSave();
                                          }).then(function() {
                                              task.running('info', {id: newApp.clonedApp.id, msg: 'Application saved'});
                                              return clonedApp;
                                          })
  
                                      });
  
                                  }
  
                                  // If a reload is required, do it!
  
                                  if (newApp.scriptReplace.reloadApp) {
  
                                      step = step.then(function(clonedApp) {
  
                                          // Reload and monitor reload progress
  
                                          var timer = setInterval(function() {
                                              global.getProgress(0).then(function(result) {
                                                  if (result.qPersistentProgress) {
                                                      var rePattern = new RegExp(reloadRegex);
                                                      var match = rePattern.exec(result.qPersistentProgress);
                                                      while (match != null) {
                                                          task.running('reload', {id: newApp.clonedApp.id, msg: match[1]});
                                                          match = rePattern.exec(result.qPersistentProgress);
                                                      }
                                                  }
                                              });
                                          }, 1000);
  
                                          return clonedApp.doReload().then(function(result) {
                                              if (result) {
                                                  clearInterval(timer);
  
                                                  task.running('info', {id: newApp.clonedApp.id, msg: 'Application reloaded'});
  
                                                  if (newApp.clonedApp.published) {
  
                                                      task.running('info', {id: newApp.clonedApp.id, msg: 'Save not needed!'});
                                                      return clonedApp;
  
                                                  } else {
  
                                                      return clonedApp.doSave().then(function() {
  
                                                          task.running('info', {id: newApp.clonedApp.id, msg: 'Application saved'});
                                                          return clonedApp;
  
                                                      });
  
                                                  }
                                              } else {
                                                  
                                                  return clonedApp.doSave().then(function() {
  
                                                      return promise.reject({id: newApp.clonedApp.id, msg: 'Application not reloaded'});
  
                                                  });
                                                  
                                                  
                                              }
                                          });
  
                                      });
                                  }
  
                                  // If a publish is required, do it!
  
                                  if (newApp.scriptReplace.publishStream) {
  
                                      step = step.then(function(clonedApp) {
  
                                          // Publish app in the given stream
  
                                          if (!newApp.scriptReplace.replaceApp) {
  
                                              // No app to replace
  
                                              return clonedApp.publish(newApp.scriptReplace.publishStream).then(function() {
                                                  task.running('info', {id: newApp.clonedApp.id, msg: 'Application published'});
                                                  return clonedApp;
                                              })
  
                                          } else {
  
                                              // There was an app to replace. We need to use QRS API to publish it
  
                                              return qrsApi.app.id(newApp.clonedApp.id).replace.put(undefined, {app: newApp.scriptReplace.replaceApp}).then(function(repApp) {
  
                                                  task.running('info', {id: newApp.clonedApp.id, msg: 'Application published (replaced)'});
  
                                                  return qrsApi.app.id(newApp.clonedApp.id).delete().then(function() {
                                                      task.running('info', {id: newApp.clonedApp.id, msg: 'Removed unpublished app'});
  
                                                      newApp.clonedApp = repApp;
  
                                                      return;
                                                  });
                                              });
                                          }
                                      });
  
                                  } else {
  
                                      // Don't publish app. Let's check if we need to replace an unpublished app
  
                                      if (newApp.scriptReplace.replaceApp && !newApp.clonedApp.published) {
  
                                          step = step.then(function() {
  
                                              return qrsApi.app.id(newApp.scriptReplace.replaceApp).delete().then(function() {
                                                  task.running('info', {id: newApp.clonedApp.id, msg: 'Removed unpublished app'});
                                                  return;
                                              });
  
                                          });
  
                                      }
  
                                  }
  
                                  // If it is not required to keep the app after all this process (who knows...), just delete it!
  
                                  if (!newApp.scriptReplace.keepApp) {
  
                                      step = step.then(function() {
  
                                          // Delete app
  
                                          return qrsApi.app.id(newApp.clonedApp.id).delete().then(function() {
                                              task.running('info', {id: newApp.clonedApp.id, msg: 'Removed cloned app'});
                                              return;
                                          });
  
                                      });
  
                                  } else {
  
                                      // Else we can check the reload task
  
                                      if (newApp.scriptReplace.createReloadTask) {
  
                                          step = step.then(function() {
  
                                              return qrsApi.reloadtask.full.get().then(function(tasks) {
                                                  return tasks.filter(function(task) {
                                                      return task.app.id == newApp.clonedApp.id;
                                                  });
                                              }).then(function(reply) {
  
                                                  if (reply.length > 0) {
                                                      task.running('warning', {id: newApp.clonedApp.id, msg: 'Reload task already exists on this app. Ignoring reload task creation...'});
                                                  } else {
                                                      task.running('info', {id: newApp.clonedApp.id, msg: 'Creating new reload task'});
  
                                                      newApp.scriptReplace.createReloadTask.task.app.id = newApp.clonedApp.id;
  
                                                      return qrsApi.reloadtask.create.post(newApp.scriptReplace.createReloadTask).then(function(reloadTask) {
                                                          task.running('info', {id: newApp.clonedApp.id, msg: 'Reload task created'});
                                                      });
  
                                                  }
  
                                              });
  
                                          });
  
                                      }
  
                                      // And the custom properties
  
                                      if (newApp.scriptReplace.customProperties) {
  
                                          step = step.then(function() {
  
                                              return qrsApi.app.id(newApp.clonedApp.id).get()
  
                                          }).then(function(appDef) {
  
                                              appDef.customProperties = newApp.scriptReplace.customProperties.map(function(customProperty) {
                                                  return customProperty.values.map(function(value) {
                                                      return {
                                                          value: value,
                                                          definition: {
                                                              id: customProperty.name
                                                          }
                                                      };
                                                  });
                                              }).reduce(function(pv, cv) {
                                                  return pv.concat(cv);
                                              });
  
                                              return qrsApi.app.id(newApp.clonedApp.id).put(appDef);
  
                                          });
  
                                      }
                                  }
  
                                  step = step.then(function() {
                                      task.running('odag', newApp.clonedApp.id);
                                      return newApp.clonedApp.id;
                                  });
  
                                  return step;
  
                              }
                          );
  
                      });
  
                      newAppsScriptReplacedStep = newAppsScriptReplacedStep.then(function(reply) {
                          newAppsScriptReplaced = newAppsScriptReplaced.concat(reply);
                          return promise.all(newAppsScriptReplacedChunk.map(function(promiseFactory) {
                              return promiseFactory();
                          }));
                      });
  
                  });
  
                  return newAppsScriptReplacedStep.then(function(reply) {
                          return newAppsScriptReplaced.concat(reply);
                  });
                  
              }).then(function(val) {
                  
                  task.done(val);
  
              }).fail(function(err) {
  
                  task.failed(err);
                  return promise.reject(err);
  
              });
              
          }
          
          if (returnObservable) {
              return task;
          } else {
              return task.toPromise(extPromise);
          }
  
      }
func_code_string: function odagMixin(options, params) {
  
          var qrsApi = qrs(options);
          
          var global = this;
          var task = new Task();
          
          var templateApp = params.templateApp;
  
          if (typeof templateApp == 'undefined') {
              return promise.reject('Please provide the templateApp parameter');
          }
  
          var maxParDup = undef.if(params.maxParDup, 1);
  
          // Handles parameters default values
  
          var defaultReplaceApp = '%(targetAppName)s';
          var defaultTargetApp = '%(templateAppName)s %(replaceValue)s';
          var defaultCreateReloadTask = {
              task: {
                  name: 'Reload for %(targetAppName)s',
                  taskType: 0,
                  enabled: true,
                  taskSessionTimeout: 1440,
                  maxRetries: 0,
                  tags: [],
                  app: {},
                  isManuallyTriggered: false,
                  customProperties: []
              },
              compositeEvents: [],
              schemaEvents: [{
                  name: 'Hourly',
                  enabled: true,
                  eventType: 0,
                  startDate: '2016-01-01T00:00:00.000Z',
                  expirationDate: '9999-12-30T23:00:00.000Z',
                  schemaFilterDescription: ['* * - * * * * *'],
                  incrementDescription: '0 1 0 0',
                  incrementOption: '1',
                  privileges: ['read','update','create','delete']
              }]
          };
  
          var replacesDef = undef.if(params.replacesDef, false);
          var duplicateApp = undef.if(params.duplicateApp, true);
          var reloadApp = undef.if(params.reloadApp, true);
          var keepApp = undef.if(params.keepApp, true);
          var overwriteApp = undef.if(params.overwriteApp, true);
          var replaceFullRow = undef.if(params.replaceFullRow, false);
          var reloadRegex = undef.if(params.reloadRegex, /(.*)/);
          var replaceApp = undef.if(params.replaceApp, defaultReplaceApp);
          var targetApp = undef.if(params.targetApp, defaultTargetApp);
          var publishStream = undef.if(params.publishStream, false);
          var createReloadTask = undef.if(params.createReloadTask, false);
          var customProperties = undef.if(params.customProperties, false);
  
          // Convert simplified syntax to advanced syntax
  
          if (replacesDef && !Array.isArray(replacesDef)) {
              replacesDef = [replacesDef];
          }
  
          replacesDef = replacesDef.map(function(item) {
              var retVal = {};
  
              if (typeof item.replaces === 'undefined' && typeof item.marker !== 'undefined' && typeof item.value !== 'undefined') {
                  var newReplace = {marker: item.marker, value: item.value};
                  if (typeof item.fullRow !== 'undefined') newReplace.fullRow = item.fullRow;
                  retVal.replaces = [newReplace];
              } else if (typeof item.replaces === 'undefined' && typeof item.marker === 'undefined' && typeof item.value === 'undefined') {
                  retVal.replaces = false;
              } else if (typeof item.replaces !== 'undefined' && !Array.isArray(item.replaces)) {
                  retVal.replaces = [item.replaces];
              } else if (typeof item.replaces !== 'undefined' && Array.isArray(item.replaces)) {
                  retVal.replaces = item.replaces;
              } else {
                  task.running('warning', 'Unhandled replace parameter: %s', item);
                  return undefined;
              }
  
              retVal.replaces.forEach(function(replace) {
                  if (!Array.isArray(replace.value))
                      replace.value = [replace.value];
              });
  
              retVal.duplicateApp = undef.if(item.duplicateApp, duplicateApp);
              retVal.reloadApp = undef.if(item.reloadApp, reloadApp);
              retVal.keepApp = undef.if(item.keepApp, keepApp);
              retVal.overwriteApp = undef.if(item.overwriteApp, overwriteApp);
              retVal.replaceApp = undef.if(item.replaceApp, replaceApp);
              retVal.targetApp = undef.if(item.targetApp, targetApp);
              retVal.publishStream = undef.if(item.publishStream, publishStream);
              retVal.createReloadTask = undef.if(item.createReloadTask, createReloadTask);
              retVal.customProperties = undef.if(item.customProperties, customProperties);
  
              if (retVal.replaceApp == true) {
                  retVal.replaceApp = defaultReplaceApp;
              }
  
              if (retVal.targetApp == true) {
                  retVal.targetApp = defaultTargetApp;
              }
  
              if (retVal.createReloadTask == true) {
                  retVal.createReloadTask = defaultCreateReloadTask;
              }
  
              if (retVal.customProperties && !Array.isArray(retVal.customProperties)) {
                  retVal.customProperties = [retVal.customProperties];
              }
  
              if (retVal.customProperties) {
                  retVal.customProperties.forEach(function(customProperty) {
                      if (customProperty.values && !Array.isArray(customProperty.values)) {
                          customProperty.values = [customProperty.values];
                      }
                  });
              }
  
              return retVal;
          }).filter(function(item) {
              return typeof item !== 'undefined';
          });
  
          replacesDef = replacesDef.map(function(item) {
              return item.replaces.map(function(replace) {
                  return replace.value.map(function(val) {
                      return {marker: replace.marker, value: val, fullRow: ((typeof replace.fullRow !== 'undefined') ? replace.fullRow : false)};
                  });
              }).reduce(function(pv, cv) {
                  var retVal = [];
                  cv.forEach(function(cvitem) {
                      pv.forEach(function(pvitem) {
                          retVal.push(pvitem.concat([cvitem]));
                      });
                  });
  
                  return retVal;
              }, [[]]).map(function(replace) {
                  return {
                      replace: replace,
                      duplicateApp: item.duplicateApp,
                      reloadApp: item.reloadApp,
                      keepApp: item.keepApp,
                      overwriteApp: item.overwriteApp,
                      replaceApp: item.replaceApp,
                      targetApp: item.targetApp,
                      publishStream: item.publishStream,
                      createReloadTask: (item.createReloadTask) ? extend(true, {}, item.createReloadTask) : false,
                      customProperties: (item.customProperties) ? extend(true, [], item.customProperties) : false
                  };
              });
          }).reduce(function(pv, cv) {
              return pv.concat(cv);
          });
  
          // If no clone task was provided, just return an empty array
  
          if (replacesDef.length == 0) {
              task.done([]);
          } else {
  
              // Else start the cloning process
  
              promise().then(function() {
  
                  // Start
  
                  task.running('info', 'Starting!');
  
              }).then(function(reply) {
  
                  // Connected
  
                  return promise.all([
                      global.getDocList().then(function(reply) {
                          task.running('info', 'Got document List');
                          return reply;
                      }),
                      global.getStreamList().then(function(reply) {
                          task.running('info', 'Got Stream List');
                          return reply;
                      }),
                      qrsApi.custompropertydefinition.get()
                  ]);
  
              }).then(function([docList, streamList, cpList]) {
  
                  // Replace stream & app names by ids
  
                  // Looping on replace definition
  
                  var pendingActions = [];
                  var missingCpValues = {};
                  var templateFound;
                  replacesDef.forEach(function(replaceDef) {
  
                      //<editor-fold desc="Make sure the publish stream is an id and not a name">
  
                      if (replaceDef.publishStream && !replaceDef.publishStream.match(/^[a-f0-9\-]{36}$/)) {
  
                          task.running('info', 'Stream to publish into is an stream name (%s), trying to find the associated id', replaceDef.publishStream);
  
                          var arrayStreamFound = streamList.filter(function(item) {
                              return (item.qName == replaceDef.publishStream);
                          });
  
                          if (arrayStreamFound.length > 1) {
                              task.running('warning', 'Several streams found : %s, using first one (%s)', arrayStreamFound.map(function(app) {
                                  return app.qId;
                              }), arrayStreamFound[0].qId);
                          } else if (arrayStreamFound.length == 0) {
                              pendingActions.push(promise.reject('Stream not found: ' + replaceDef.publishStream));
                              return;
                          } else {
                              task.running('info', 'Stream found');
                          }
  
                          replaceDef.publishStream = arrayStreamFound[0].qId;
  
                      } else if (replaceDef.publishStream) {
  
                          task.running('info', 'Stream to publish into is an id (%s), checking if it exists', replaceDef.publishStream);
  
                          var arrayStreamFound = streamList.filter(function(item) {
                              return (item.qId == replaceDef.publishStream);
                          });
  
                          if (arrayStreamFound.length == 0) {
                              pendingActions.push(promise.reject('No :( Stream not found: ' + replaceDef.publishStream));
                              return;
                          } else {
                              task.running('info', 'Yes! Stream found');
                          }
  
                      }
  
                      //</editor-fold>
  
                      //<editor-fold desc="Make sure the template app is an id and not a name">
  
                      var arrayTemplateFound = docList.filter(function(item) {
                          return (templateApp.match(/^[a-f0-9\-]{36}$/) && item.qDocId == templateApp) ||
                              (!templateApp.match(/^[a-f0-9\-]{36}$/) && item.qDocName == templateApp);
                      });
  
                      if (arrayTemplateFound.length > 1) {
                          task.running('warning', 'Several apps found : %s, using first one (%s)', arrayTemplateFound.map(function(app) {
                              return app.qDocId;
                          }), arrayTemplateFound[0].qDocId);
                      } else if (arrayTemplateFound.length == 0) {
                          pendingActions.push(promise.reject('Template not found: ' + templateApp));
                          return;
                      } else {
                          task.running('info', 'Template found');
                      }
  
                      templateFound = arrayTemplateFound[0];
  
                      //</editor-fold>
  
                      //<editor-fold desc="Replace sprintf values">
  
                      var replaceDic = docList.map(function(doc) {
                          return {key: doc.qDocId, value: doc.qDocName};
                      }).reduce(function(pv, cv) {
                          pv[cv.key] = cv.value;
                          return pv;
                      }, {});
  
                      var replaceValue = replaceDef.replace.map(function(item) {
                          return item.value;
                      }).reduce(function(pv, cv) {
                          return sprintf('%s %s', pv, cv);
                      });
  
                      if (replaceDef.targetApp)
                          replaceDef.targetApp = sprintf(replaceDef.targetApp, {templateAppId: arrayTemplateFound[0].qDocId, templateAppName: replaceDic[arrayTemplateFound[0].qDocId], replaceValue: replaceValue, replace: replaceDef.replace});
  
                      if (replaceDef.replaceApp)
                          replaceDef.replaceApp = sprintf(replaceDef.replaceApp, {templateAppId: arrayTemplateFound[0].qDocId, templateAppName: replaceDic[arrayTemplateFound[0].qDocId], replaceValue: replaceValue, replace: replaceDef.replace, targetAppName: replaceDef.targetApp});
  
                      if (replaceDef.createReloadTask && replaceDef.createReloadTask.task && replaceDef.createReloadTask.task.name)
                          replaceDef.createReloadTask.task.name = sprintf(replaceDef.createReloadTask.task.name, {templateAppId: arrayTemplateFound[0].qDocId, templateAppName: replaceDic[arrayTemplateFound[0].qDocId], replaceValue: replaceValue, replace: replaceDef.replace, targetAppName: replaceDef.targetApp});
  
                      if (replaceDef.customProperties)
                          replaceDef.customProperties.forEach(function(customProperty) {
                              if (customProperty.values) {
                                  customProperty.values = customProperty.values.map(function(value) {
                                      return sprintf(value, {templateAppId: arrayTemplateFound[0].qDocId, templateAppName: replaceDic[arrayTemplateFound[0].qDocId], replaceValue: replaceValue, replace: replaceDef.replace, targetAppName: replaceDef.targetApp});
                                  });
                              }
                          });
  
                      //</editor-fold>
  
                      //<editor-fold desc="Make sure the replace apps are ids and not names">
  
                      if (replaceDef.replaceApp && !replaceDef.replaceApp.match(/^[a-f0-9\-]{36}$/)) {
  
                          task.running('info', 'App to replace is an app name (%s), trying to find the associated id', replaceDef.replaceApp);
  
                          var arrayAppFound = docList.filter(function(item) {
                              return (
                                  item.qDocName == replaceDef.replaceApp &&
                                  (
                                      (replaceDef.publishStream && item.qMeta.stream && item.qMeta.stream.id == replaceDef.publishStream) ||
                                      (!replaceDef.publishStream && !item.published)
                                  )
                              );
                          });
  
                          if (arrayAppFound.length > 1) {
                              task.running('warning', 'Several apps found : %s, using first one (%s)', arrayAppFound.map(function(app) {
                                  return app.qDocId;
                              }), arrayAppFound[0].qDocId);
                              replaceDef.replaceApp = arrayAppFound[0].qDocId;
                          } else if (arrayAppFound.length == 0) {
                              task.running('warning', 'App not found (%s)', replaceDef.replaceApp);
                              replaceDef.replaceApp = false;
                          } else {
                              task.running('info', 'App found');
                              replaceDef.replaceApp = arrayAppFound[0].qDocId;
                          }
  
                      } else if (replaceDef.replaceApp) {
  
                          task.running('info', 'App to replace is an id (%s), checking if it exists', replaceDef.replaceApp);
  
                          var arrayAppFound = docList.filter(function(item) {
                              return (item.qDocId == replaceDef.replaceApp && item.qMeta.stream && item.qMeta.stream.id == replaceDef.publishStream);
                          });
  
                          if (arrayAppFound.length == 0) {
                              task.running('warning', 'App not found (%s)', replaceDef.replaceApp);
                              replaceDef.replaceApp = false;
                          } else {
                              task.running('info', 'App found');
                          }
  
                      }
  
                      //</editor-fold>
  
                      //<editor-fold desc="Make sure custom properties are ids and that values exist">
  
                      if (replaceDef.customProperties) {
  
                          replaceDef.customProperties.forEach(function(customProperty) {
  
                              var arrayCpFound = cpList.filter(function(item) {
                                  return (item.name == customProperty.name && !customProperty.name.match(/^[a-f0-9\-]{36}$/)) ||
                                      (item.id == customProperty.name && customProperty.name.match(/^[a-f0-9\-]{36}$/));
                              });
  
                              if (arrayCpFound.length > 1) {
                                  task.running('warning', 'Several matching custom properties found : %s, using first one (%s)', arrayCpFound.map(function(cp) {
                                      return cp.id;
                                  }), arrayCpFound[0].id);
                              } else if (arrayCpFound.length == 0) {
                                  pendingActions.push(promise.reject('Custom property not found: ' + customProperty.name));
                                  return;
                              } else {
                                  task.running('info', 'Custom property found');
                              }
  
                              customProperty.name = arrayCpFound[0].id;
  
                              if (typeof missingCpValues[arrayCpFound[0].id] === 'undefined') {
                                  missingCpValues[arrayCpFound[0].id] = [];
                              }
  
                              var missingValues = missingCpValues[arrayCpFound[0].id];
                              customProperty.values.forEach(function(value) {
                                  if (arrayCpFound[0].choiceValues.indexOf(value) < 0 && missingValues.indexOf(value) < 0) {
                                      missingValues.push(value);
                                  }
                              });
                          });
  
                      }
  
                      //</editor-fold>
  
                  });
  
                  Object.keys(missingCpValues).forEach(function(cpkey) {
                      var missingValues = missingCpValues[cpkey];
  
                      var addToCp = qrsApi.custompropertydefinition.id(cpkey).get().then(function(cp) {
  
                          if (cp.objectTypes.indexOf('App') < 0) {
                              return promise.reject('Custom property is not applicable to Apps...');
                          }
  
                          if (missingValues.length > 0) {
                              task.running('warning', 'Adding missing values in custom property: %s', missingValues);
                              cp.choiceValues = cp.choiceValues.concat(missingValues);
                              return qrsApi.custompropertydefinition.id(cpkey).put(cp);
                          }
  
                      });
  
                      pendingActions.push(addToCp);
                  });
  
                  return promise.all(pendingActions).then(function() {
                      return promise.all([
                          templateFound.qDocId,
                          templateFound.qDocName
                      ]);
                  });
  
              }).then(function([templateId, templateName]) {
  
                  // Clone app
  
                  var newAppsStep = promise.resolve([]);
                  var newApps = [];
                  arrayDivide(replacesDef, maxParDup).forEach(function(scriptReplacesChunk) {
                      var newAppsChunk = [];
  
                      scriptReplacesChunk.forEach(function(scriptReplace) {
                          newAppsChunk.push(
                              function() {
  
                                  if (!scriptReplace.duplicateApp) {
  
                                      task.running('warning', 'Working directly on template app (%s)!', templateId);
  
                                      return qrsApi.app.id(templateId).get().then(function(reply) {
                                          scriptReplace.publishStream = (reply.published ? false : scriptReplace.publishStream);
                                          scriptReplace.replaceApp = false;
  
                                          return {
                                              scriptReplace: scriptReplace,
                                              clonedApp: reply
                                          };
                                      });
  
                                  } else if (scriptReplace.replaceApp && !scriptReplace.overwriteApp) {
  
                                      task.running('info', 'App already exists! Working on existing app (%s)', scriptReplace.replaceApp);
  
                                      return qrsApi.app.id(scriptReplace.replaceApp).get().then(function(reply) {
                                          scriptReplace.publishStream = (reply.published ? false : scriptReplace.publishStream);
                                          scriptReplace.replaceApp = false;
  
                                          return {
                                              scriptReplace: scriptReplace,
                                              clonedApp: reply
                                          };
                                      });
  
                                  } else {
  
                                      var targetAppName = scriptReplace.targetApp;
                                      task.running('info', 'Generating clone named \'%s\'', targetAppName);
  
                                      return qrsApi.app.id(templateId).copy.post(undefined, {name: targetAppName}).then(function(clonedApp) {
                                          task.running('info', 'Clone named \'%s\' created successfully with id: %s', targetAppName, clonedApp.id);
                                          return {
                                              scriptReplace: scriptReplace,
                                              clonedApp: clonedApp
                                          };
                                      });
                                  }
                              }
                          );
                      });
  
                      newAppsStep = newAppsStep.then(function(reply) {
                          newApps = newApps.concat(reply);
                          return promise.all(newAppsChunk.map(function(promiseFactory) {
                              return promiseFactory();
                          }));
                      });
  
                  });
  
                  return newAppsStep.then(function(reply) {
                      return newApps.concat(reply);
                  });
  
              }).then(function(newApps) {
  
                  // Perform modifications on clone
  
                  // Replace script marker and reload in //
                  // Only if marker is not null!
  
                  var newAppsScriptReplacedStep = promise.resolve([]);
                  var newAppsScriptReplaced = [];
                  arrayDivide(newApps, maxParDup).forEach(function(newAppsChunk) {
  
                      var newAppsScriptReplacedChunk = [];
                      newAppsChunk.forEach(function(newApp) {
  
                          newAppsScriptReplacedChunk.push(
                              function() {
  
                                  var step = promise();
  
                                  // If nothing to do just return
  
                                  if (!(newApp.scriptReplace.replace || newApp.scriptReplace.reloadApp || newApp.scriptReplace.publishStream)) return promise.resolve([]);
  
                                  // If anything to do, open socket
  
                                  if (newApp.scriptReplace.replace || newApp.scriptReplace.reloadApp) {
  
                                      step = step.then(function() {
  
                                          return global.openDoc(newApp.clonedApp.id).then(function(reply) {
                                              task.running('info', {id: newApp.clonedApp.id, msg: 'Doc opened'});
                                              return reply;
                                          })
  
                                      });
  
                                  }
  
                                  // If a replace is required, do it!
  
                                  if (newApp.scriptReplace.replace) {
  
                                      step = step.then(function(clonedApp) {
  
                                          // Replace the script marker by the replace value
  
                                          task.running('info', {id: newApp.clonedApp.id, msg: 'Application opened'});
  
                                          return promise().then(function() {
                                              return clonedApp.getScript();
                                          }).then(function(result) {
                                              task.running('info', {id: newApp.clonedApp.id, msg: 'Application script extracted'});
  
                                              var newScript = result;
                                              newApp.scriptReplace.replace.forEach(function(replace) {
                                                  if (replace.fullRow) {
                                                      var re = new RegExp(sprintf('^.*%s.*$', replace.marker), 'm');
                                                      newScript = newScript.replace(re, sprintf('%s // %s', replace.value, replace.marker));
                                                  } else {
                                                      newScript = newScript.replace(replace.marker, replace.value);
                                                  }
                                              });
  
                                              return clonedApp.setScript(newScript);
                                          }).then(function() {
                                              task.running('info', {id: newApp.clonedApp.id, msg: 'Application script replaced'});
                                              return clonedApp.doSave();
                                          }).then(function() {
                                              task.running('info', {id: newApp.clonedApp.id, msg: 'Application saved'});
                                              return clonedApp;
                                          })
  
                                      });
  
                                  }
  
                                  // If a reload is required, do it!
  
                                  if (newApp.scriptReplace.reloadApp) {
  
                                      step = step.then(function(clonedApp) {
  
                                          // Reload and monitor reload progress
  
                                          var timer = setInterval(function() {
                                              global.getProgress(0).then(function(result) {
                                                  if (result.qPersistentProgress) {
                                                      var rePattern = new RegExp(reloadRegex);
                                                      var match = rePattern.exec(result.qPersistentProgress);
                                                      while (match != null) {
                                                          task.running('reload', {id: newApp.clonedApp.id, msg: match[1]});
                                                          match = rePattern.exec(result.qPersistentProgress);
                                                      }
                                                  }
                                              });
                                          }, 1000);
  
                                          return clonedApp.doReload().then(function(result) {
                                              if (result) {
                                                  clearInterval(timer);
  
                                                  task.running('info', {id: newApp.clonedApp.id, msg: 'Application reloaded'});
  
                                                  if (newApp.clonedApp.published) {
  
                                                      task.running('info', {id: newApp.clonedApp.id, msg: 'Save not needed!'});
                                                      return clonedApp;
  
                                                  } else {
  
                                                      return clonedApp.doSave().then(function() {
  
                                                          task.running('info', {id: newApp.clonedApp.id, msg: 'Application saved'});
                                                          return clonedApp;
  
                                                      });
  
                                                  }
                                              } else {
                                                  
                                                  return clonedApp.doSave().then(function() {
  
                                                      return promise.reject({id: newApp.clonedApp.id, msg: 'Application not reloaded'});
  
                                                  });
                                                  
                                                  
                                              }
                                          });
  
                                      });
                                  }
  
                                  // If a publish is required, do it!
  
                                  if (newApp.scriptReplace.publishStream) {
  
                                      step = step.then(function(clonedApp) {
  
                                          // Publish app in the given stream
  
                                          if (!newApp.scriptReplace.replaceApp) {
  
                                              // No app to replace
  
                                              return clonedApp.publish(newApp.scriptReplace.publishStream).then(function() {
                                                  task.running('info', {id: newApp.clonedApp.id, msg: 'Application published'});
                                                  return clonedApp;
                                              })
  
                                          } else {
  
                                              // There was an app to replace. We need to use QRS API to publish it
  
                                              return qrsApi.app.id(newApp.clonedApp.id).replace.put(undefined, {app: newApp.scriptReplace.replaceApp}).then(function(repApp) {
  
                                                  task.running('info', {id: newApp.clonedApp.id, msg: 'Application published (replaced)'});
  
                                                  return qrsApi.app.id(newApp.clonedApp.id).delete().then(function() {
                                                      task.running('info', {id: newApp.clonedApp.id, msg: 'Removed unpublished app'});
  
                                                      newApp.clonedApp = repApp;
  
                                                      return;
                                                  });
                                              });
                                          }
                                      });
  
                                  } else {
  
                                      // Don't publish app. Let's check if we need to replace an unpublished app
  
                                      if (newApp.scriptReplace.replaceApp && !newApp.clonedApp.published) {
  
                                          step = step.then(function() {
  
                                              return qrsApi.app.id(newApp.scriptReplace.replaceApp).delete().then(function() {
                                                  task.running('info', {id: newApp.clonedApp.id, msg: 'Removed unpublished app'});
                                                  return;
                                              });
  
                                          });
  
                                      }
  
                                  }
  
                                  // If it is not required to keep the app after all this process (who knows...), just delete it!
  
                                  if (!newApp.scriptReplace.keepApp) {
  
                                      step = step.then(function() {
  
                                          // Delete app
  
                                          return qrsApi.app.id(newApp.clonedApp.id).delete().then(function() {
                                              task.running('info', {id: newApp.clonedApp.id, msg: 'Removed cloned app'});
                                              return;
                                          });
  
                                      });
  
                                  } else {
  
                                      // Else we can check the reload task
  
                                      if (newApp.scriptReplace.createReloadTask) {
  
                                          step = step.then(function() {
  
                                              return qrsApi.reloadtask.full.get().then(function(tasks) {
                                                  return tasks.filter(function(task) {
                                                      return task.app.id == newApp.clonedApp.id;
                                                  });
                                              }).then(function(reply) {
  
                                                  if (reply.length > 0) {
                                                      task.running('warning', {id: newApp.clonedApp.id, msg: 'Reload task already exists on this app. Ignoring reload task creation...'});
                                                  } else {
                                                      task.running('info', {id: newApp.clonedApp.id, msg: 'Creating new reload task'});
  
                                                      newApp.scriptReplace.createReloadTask.task.app.id = newApp.clonedApp.id;
  
                                                      return qrsApi.reloadtask.create.post(newApp.scriptReplace.createReloadTask).then(function(reloadTask) {
                                                          task.running('info', {id: newApp.clonedApp.id, msg: 'Reload task created'});
                                                      });
  
                                                  }
  
                                              });
  
                                          });
  
                                      }
  
                                      // And the custom properties
  
                                      if (newApp.scriptReplace.customProperties) {
  
                                          step = step.then(function() {
  
                                              return qrsApi.app.id(newApp.clonedApp.id).get()
  
                                          }).then(function(appDef) {
  
                                              appDef.customProperties = newApp.scriptReplace.customProperties.map(function(customProperty) {
                                                  return customProperty.values.map(function(value) {
                                                      return {
                                                          value: value,
                                                          definition: {
                                                              id: customProperty.name
                                                          }
                                                      };
                                                  });
                                              }).reduce(function(pv, cv) {
                                                  return pv.concat(cv);
                                              });
  
                                              return qrsApi.app.id(newApp.clonedApp.id).put(appDef);
  
                                          });
  
                                      }
                                  }
  
                                  step = step.then(function() {
                                      task.running('odag', newApp.clonedApp.id);
                                      return newApp.clonedApp.id;
                                  });
  
                                  return step;
  
                              }
                          );
  
                      });
  
                      newAppsScriptReplacedStep = newAppsScriptReplacedStep.then(function(reply) {
                          newAppsScriptReplaced = newAppsScriptReplaced.concat(reply);
                          return promise.all(newAppsScriptReplacedChunk.map(function(promiseFactory) {
                              return promiseFactory();
                          }));
                      });
  
                  });
  
                  return newAppsScriptReplacedStep.then(function(reply) {
                          return newAppsScriptReplaced.concat(reply);
                  });
                  
              }).then(function(val) {
                  
                  task.done(val);
  
              }).fail(function(err) {
  
                  task.failed(err);
                  return promise.reject(err);
  
              });
              
          }
          
          if (returnObservable) {
              return task;
          } else {
              return task.toPromise(extPromise);
          }
  
      }
func_documentation_string: @typedef {Object} cloneDef
  @param {Array.<replaceDef>|replaceDef} replaces the list of values to be replaced. If this parameter is null or false, the script is not updated
  @param {boolean} [duplicateApp=true] duplicates the template app. Overrides parent duplicateApp if defined
  @param {boolean} [reloadApp=parent value] reload the app. Overrides parent reloadApp if defined
  @param {boolean} [keepApp=parent value] keep the app when the operation finished. If false, app will be deleted. Overrides parent keepApp if defined
  @param {boolean} [overwriteApp=parent value] Overwrite the replace app if it exists. If false, and if replace app exists, nothing will happen. Overrides parent replaceApp if defined
  @param {boolean|string} [replaceApp=parent value] name or id of the app to replace. Can also be boolean (when false, don't replace the app, when true, replace the app which name == targetApp). Overrides parent replaceApp if defined
  @param {string} [targetApp=parent value] name of the new app. Overrides parent targetApp if defined
  @param {string} [publishStream] name or id of the stream to publish the new app in. If this parameter is null or false, then the app is not published. Overrides parent publishStreamId if defined
  @param {boolean|Qlik.reloadTask} [createReloadTask=true]  create a reload task associated to the generated app. If true, create a task with a daily trigger @ 4AM. If a reload task already exists it will be overwritten. If null or false, do nothing. Overrides parent createReloadTask if defined
  @param {Object} [customProperties] custom properties to add to the newly created app. If this parameter is null or false, then the custom properties are not changed. Overrides parent customProperties if defined
   
  Duplicates a template app, updates its script, reloads it and publishes it
  
  @example Simple mode 1 : duplicate a template app, replace a marker in the script, reload and publish (overwrites if target app exists in stream)
  ```javascript
  var task = new utils.Core.Task();
  task.start();
  
  task.bind(function(task) {
  console.log(task.val, task.detail);
  });
  
  readFile(testQlikSensePfx).then(function(pfx) {
  
  task.running('info', 'certificate loaded...');
  
  return utils.Qlik.dynamicAppClone({
  restUri: 'http://10.20.30.40',
  pfx: pfx,
  UserId: 'qlikservice',
  UserDirectory: '2008R2-0'
  },
  {
  templateApp: '3bcb8ed0-7ac5-4cd0-8913-37d1255d67c3',
  replacesDef: { marker: '%Replace me!%', value: 'Employees.qvd' },
  publishStream: 'aaec8d41-5201-43ab-809f-3063750dfafd'
  },
  task: task
  });
  
  });
  ```
  
  @example Simple mode 2 : duplicate a template app, replace 3 markers in the script, reload and don't publish
  ```javascript
  var task = new utils.Core.Task();
  task.start();
  
  task.bind(function(task) {
  console.log(task.val, task.detail);
  });
  
  readFile(testQlikSensePfx).then(function(pfx) {
  
  task.running('info', 'certificate loaded...');
  
  return utils.Qlik.dynamicAppClone({
  restUri: 'http://10.20.30.40',
  pfx: pfx,
  UserId: 'qlikservice',
  UserDirectory: '2008R2-0'
  },
  {
  templateApp: '3bcb8ed0-7ac5-4cd0-8913-37d1255d67c3',
  replacesDef: [
  { marker: '%Replace me1!%', value: 'Employees1.qvd' },
  { marker: '%Replace me2!%', value: 'Employees2.qvd' },
  { marker: '%Replace me3!%', value: 'Employees3.qvd' }
  ]
  },
  task: task
  });
  
  });
  ```
  
  @example Advanced mode 1: clone a template app twice:
  - first app will be called 'White App' and will have the markers replaced to 1 & 2 qvds. Don't reload it but save it. When publishing it, replace the app with name 'White App' already existing in the stream.
  - second app will be called 'Black App' and will have the markers replaced to 3 & 4 qvds. Reload it. Don't save it (delete the copy). Don't publish it.
  
  ```javascript
  var task = new utils.Core.Task();
  task.start();
  
  task.bind(function(task) {
  console.log(task.val, task.detail);
  });
  
  readFile(testQlikSensePfx).then(function(pfx) {
  
  task.running('info', 'certificate loaded...');
  
  return utils.Qlik.dynamicAppClone({
  restUri: 'http://10.20.30.40',
  pfx: pfx,
  UserId: 'qlikservice',
  UserDirectory: '2008R2-0'
  },
  {
  templateApp: '3bcb8ed0-7ac5-4cd0-8913-37d1255d67c3',
  replacesDef: [
  {
  replaces: [
  { marker: '%Replace me1!%', value: 'Employees1.qvd' }
  { marker: '%Replace me2!%', value: 'Employees2.qvd' }
  ],
  replaceApp: 'White App',
  reloadApp = false,
  targetApp: 'White App'
  },
  {
  replaces: [
  { marker: '%Replace me1!%', value: 'Employees3.qvd' }
  { marker: '%Replace me2!%', value: 'Employees4.qvd' }
  ],
  keepApp = false,
  targetApp: 'Black App'
  publishStream: false
  }
  ],
  reloadApp = true,
  replaceApp = false,
  publishStream: 'aaec8d41-5201-43ab-809f-3063750dfafd'
  },
  task: task
  });
  
  });
  ```
  
  @memberOf Qlik
  
  @param {options}                                             options                         Qlik Sense connection options
  @param {string}                                              params                          parameters for the cloning
  @param {string}                                              params.templateApp              name or id of the template application
  @param {int}                                                 [params.maxParDup=1]            maximum number of // operations (opened sockets & QRS API queries)
  @param {Array.<replaceDef|cloneDef>|replaceDef|cloneDef}     [params.replacesDef]            definition of script replacement(s) to perform and parameters of the app to generate. If this parameter is null or false, the script is not updated
  @param {boolean}                                             [params.duplicateApp=true]      duplicates the template app
  @param {boolean}                                             [params.reloadApp=true]         reload the app
  @param {boolean}                                             [params.keepApp=true]           keep the app when the operation finished. If false, app will be deleted
  @param {boolean}                                             [params.overwriteApp=true]      Overwrite the replace app if it exists. If false, and if replace app exists, nothing will happen
  @param {RegExp}                                              [params.reloadRegex=/(.*)/]     regex to track in the script reload trace. If this parameter is null or false, uses default regex that captures everything
  @param {boolean|string}                                      [params.replaceApp=true]        name or id of the app to replace. Can also be boolean (when false, don't replace the app, when true, replace the app with name == targetApp)
  @param {string}                                              [params.targetApp=default]      name of the new app. Defaults to %(templateAppName)s %(replace.value)s
  @param {string}                                              [params.publishStream]          name or id of a stream to publish the new app in. If this parameter is null or false, then the app is not published
  @param {boolean|Qlik.reloadTask}                             [params.createReloadTask=true]  create a reload task associated to the generated app. If true, create a task with a daily trigger @ 4AM. If a reload task already exists it will be overwritten. If null or false, do nothing.
  @param {Object}                                              [params.customProperties]       custom properties to add to the newly created app. If this parameter is null or false, then the custom properties are not changed.
  @param {Task}                                                task                            task that will trace the cloning progress
  @returns {Promise}                                                                           a promise that resolves when the process is finished