text: /* global commandOptions */
  'use strict';
  
  const getNGCLIManifest = require('../../lib/CLUtils').getNGCLIManifest;
  const getNGCLIPath = require('../../lib/CLUtils').getNGCLIPath;
  const ngCLIPKG = getNGCLIManifest();
  const path = require('path');
  
  const ngCLIPKGName = ngCLIPKG.name;
  const ngCLIPath = getNGCLIPath(ngCLIPKGName);
  
  let devServer;
  try {
    devServer = require('webpack-dev-server');
  } catch (e) {
    devServer = require('@angular/cli/node_modules/webpack-dev-server/lib/Server');
    require.cache['webpack-dev-server'] = require('@angular/cli/node_modules/webpack-dev-server/lib/Server');
  }
  const Serve = require(`${ngCLIPath}/commands/serve`).default;
  const intercept = require('intercept-stdout');
  const semver = require('semver');
  
  const extractNGEvent = require('../utils/extractNGEvent');
  const wsManager = require('../services/WSConnectionsManager');
  
  const NGEventType = require('../utils/consts').NGEventType;
  const BuildStatus = require('../utils/consts').BuildStatus;
  const NGServerStatus = require('../utils/consts').NGServerStatus;
  
  const getRunningInstallations = require('../../lib/utils').getRunningInstallations;
  
  const getNPMPackage = require('../../lib/utils').getNPMPackage;
  
  const injectComponentMetadata = require('../../lib/CLUtils').injectComponentMetadata;
  const injectCLBootstrapScript = require('../../lib/CLUtils').injectCLBootstrapScript;
  
  const CLEnablementStatusManager = require('../../lib/CLEnablementStatusManager').CLEnablementStatusManager;
  
  const supportedVersions = require('../../supported_versions.json').supported_versions;
  
  const debug = require('debug')('angular-ide');
  const chalk = require('chalk');
  
  let middleware = null;
  
  const Q = require('q');
  
  const aliveMessageData = {
    serverId: Date.now(),
    projectPath: null,
    commandOptions: null,
    command: 'registerServer',
    method: 'registerServer',
  };
  
  const ngServeStatus = {
    serverStatus: NGServerStatus.STARTING,
    build: {
      status: BuildStatus.NOT_READY,
      errors: [],
    },
  };
  
  module.exports.patchServeCommand = function patchServeCommand(cliVersion) {
  
    if (semver.satisfies(cliVersion, '>=6.0.0')) {
      /**
       * TODO: Extend Serve command so it doesn't throw an error when webclipse-launch-id is passed.
       * Let just filter the argument for now as it only used as a way to identify the process by Eclipse
       */
      process.argv = process.argv.filter((argument) => argument.indexOf('--webclipse-launch-id') === -1);
      let ServeModule = null;
      if (semver.satisfies(cliVersion, '>=6.2.0')) {
        ServeModule = require('../../../@angular/cli/commands/serve-impl');
        require.cache['@angular/cli/commands/serve'] = require('../../../@angular/cli/commands/serve-impl');
      } else {
        ServeModule = require('../../../@angular/cli/commands/serve');
        require.cache['@angular/cli/commands/serve'] = require('../../../@angular/cli/commands/serve');
      }
  
      let ServeCommand = null;
  
      // ServeCommand is not available in ServeModule as "default" property in 6.1.0>=
      if (ServeModule.default) {
        ServeCommand = ServeModule.default;
      } else {
        ServeCommand = ServeModule.ServeCommand;
      }
  
      class PatchedServeCommand extends ServeCommand {
        run(commandOptions) {
          const superRun = super.run.bind(this);
  
          return new Promise((resolve) => {
  
            const originalListen = devServer.prototype.listen;
  
            devServer.prototype.listen = function () {
              middleware = this.middleware;
              return originalListen.apply(this, arguments);
            };
  
            const ngCLIPackage = ngCLIPKG;
            const ngIDEPackage = getNPMPackage('angular-ide');
            debug(`angular-cli version detected: ${ngCLIPackage.version}`);
            debug(`angular-ide version detected: ${ngIDEPackage.version}`);
  
            const prereleaseParts = semver.prerelease(ngCLIPackage.version);
            if (prereleaseParts &&
              prereleaseParts.length > 1 &&
              typeof prereleaseParts[1] === 'string'
            ) {
              ngCLIPackage.version = ngCLIPackage.version.replace(`${prereleaseParts[0]}.${prereleaseParts[1]}`,
                `${prereleaseParts[0]}.${parseInt(prereleaseParts[1])}`
              );
              debug('Normalizing package version with', ngCLIPackage.version);
            }
  
            const angularCLIVersionSupported = supportedVersions.find((versionRules) => {
              return semver.satisfies(ngCLIPackage.version, versionRules['angular-cli'], false) &&
                semver.satisfies(ngIDEPackage.version, versionRules['angular-ide'], false);
            });
  
            if (angularCLIVersionSupported) {
              debug('angular-cli version supported!');
            } else {
              debug(chalk.red('angular-cli version not supported supported!'));
            }
  
            aliveMessageData.commandOptions = commandOptions;
  
            // Available @angular@cli@7.0.0<
            if (this.project) {
              aliveMessageData.projectPath = this.project.root;
            // Available @angular@cli@7.0.0>=
            } else if (this.workspace.root) {
              aliveMessageData.projectPath = this.workspace.root;
            }          
  
            wsManager.onConnect(JSON.stringify(aliveMessageData));
            wsManager.registerServerStatus(ngServeStatus);
  
            if (process.env.NG_IDE_PORT) {
              wsManager.register(process.env.NG_IDE_PORT);
            } else {
              debug('Server port env part not set, falling back to port detection in locations file');
              
              getRunningInstallations()
              .subscribe(installations => {
                installations.forEach(installation => {
                    debug(`Installation found, connecting to port: ${installation.port}`);
                    wsManager.register(installation.port);
                  });
                });
            }
  
            /*
            * Intercepting stdout to extract relevant info
            * e.g. Where the dev server is running, error build, etc.
            */
            intercept((text) => {
              const ngEvent = extractNGEvent(text);
  
              if (ngEvent) {
                switch (ngEvent.type) {
                  case NGEventType.NG_SERVER_STATUS:
                    ngServeStatus.serverStatus = ngEvent.params.status;
                    wsManager.sendEventToAll('ng-serve-status-update');
                    break;
  
                  case NGEventType.BUILD_STATUS:
                    // Resetting build error when a new build starts
                    if (ngEvent.params.status === BuildStatus.BUILD_STARTED) {
                      ngServeStatus.build.errors = [];
                    }
  
                    ngServeStatus.build.status = ngEvent.params.status;
                    wsManager.sendEventToAll('ng-serve-status-update');
                    break;
  
                  case NGEventType.BUILD_ERROR:
                    ngServeStatus.build.errors.push(ngEvent.params);
                    break;
  
                  default:
                    // no-op
                }
              }
  
              return text;
            });
  
            let timeoutOnProgress = null;
            wsManager.on('message', function (ws, data) {
              const message = JSON.parse(data);
              switch (message.command) {
                case 'terminate':
                process.exit();
                break;
                case 'injectCL':
                  if (commandOptions.prod == null) {
                    CLEnablementStatusManager.setStatus(message.commandOptions.inject);
                    if (CLEnablementStatusManager.getStatus()) {
  
                      const bootstrapScriptConfig = {
                        host: '127.0.0.1',
                        port: null,
                      };
  
                      if (process.env.NG_IDE_PORT) {
                        debug(`[inject-cl] Using env var port: ${process.env.NG_IDE_PORT}`);
            
                        bootstrapScriptConfig.port = process.env.NG_IDE_PORT;
                        try {
                          injectCLBootstrapScript(ngCLIPackage.version, bootstrapScriptConfig);
                        } catch (e) {
                          console.log('[angular-ide] Error while injecting bootstrap script');
                        }
  
                        try {
                          injectComponentMetadata(ngCLIPackage.version);
                        } catch (e) {
                          console.log('[angular-ide] Error while injecting component metadata');
                        }
                        superRun(commandOptions);
                      } else {
                        debug(`[inject-cl] Environment var not set`);
                        getRunningInstallations()
                          .subscribe(installations => {
                            if (installations.length > 0) {
                              const sortedInstallations = installations.sort((installA, installB) => {
                                return installA.used < installB.used;
                              });
  
                              const installation = installations[0];
  
                              bootstrapScriptConfig.port = installation.port;
                              debug(`[inject-cl] Running installation detected with port ${bootstrapScriptConfig.port}`);
                              try {
                                injectCLBootstrapScript(ngCLIPackage.version, bootstrapScriptConfig);
                              } catch (e) {
                                console.log('[angular-ide] Error while injecting bootstrap script');
                              }
  
                              try {
                                injectComponentMetadata(ngCLIPackage.version);
                              } catch (e) {
                                console.log('[angular-ide] Error while injecting component metadata');
                              }
                              superRun(commandOptions);
                            }
                          });
                      }
                    } else {
                      superRun(commandOptions);
                    }
                  } else {
                    superRun(commandOptions);
                  }
                  break;
                default:
                // TODO: Handle unknown command
              }
  
            }.bind(this));
  
          });
        }
      }
  
      if (ServeModule.default) {
        ServeModule.default = PatchedServeCommand;
      } else {
        ServeModule.ServeCommand = PatchedServeCommand;
      }
    } else {
  
      // add webclipse-launch-id to supported options
      Serve.prototype.availableOptions.push(
        { name: 'webclipse-launch-id', type: String }
      );
  
      const ServeCommandPatched = Serve.extend({
        run(commandOptions) {
          let ServeTask = require(`@angular/cli/tasks/serve`).default;
  
          ServeTask.prototype.originalRun = ServeTask.prototype.run;
  
          ServeTask.prototype.run = function monkeyPatchedRun(serveTaskOptions, rebuildDoneCb) {
            // Disabling evalSourcemaps, required until proper evalSourcemaps is implemented
            // If evalSourcemaps exists and it is true, make it false
            if (serveTaskOptions.evalSourcemaps) {
              serveTaskOptions.evalSourcemaps = false;
            }
            return this.originalRun(serveTaskOptions, rebuildDoneCb);
          };
  
          const originalListen = devServer.prototype.listen;
  
          devServer.prototype.listen = function () {
            middleware = this.middleware;
            return originalListen.apply(this, arguments);
          };
  
          const ngCLIPackage = ngCLIPKG;
          const ngIDEPackage = getNPMPackage('angular-ide');
  
          debug(`angular-cli version detected: ${ngCLIPackage.version}`);
          debug(`angular-ide version detected: ${ngIDEPackage.version}`);
  
          const prereleaseParts = semver.prerelease(ngCLIPackage.version);
          if (prereleaseParts &&
            prereleaseParts.length > 1 &&
            typeof prereleaseParts[1] === 'string'
          ) {
            ngCLIPackage.version = ngCLIPackage.version.replace(`${prereleaseParts[0]}.${prereleaseParts[1]}`,
              `${prereleaseParts[0]}.${parseInt(prereleaseParts[1])}`
            );
            debug('Normalizing package version with', ngCLIPackage.version);
          }
  
          const angularCLIVersionSupported = supportedVersions.find((versionRules) => {
            return semver.satisfies(ngCLIPackage.version, versionRules['angular-cli'], false) &&
              semver.satisfies(ngIDEPackage.version, versionRules['angular-ide'], false);
          });
  
          if (angularCLIVersionSupported) {
            debug('angular-cli version supported!');
          } else {
            debug(chalk.red('angular-cli version not supported supported!'));
          }
  
          try {
            injectComponentMetadata(ngCLIPackage.version);
          } catch (e) {
            console.log('[angular-ide] Error while injecting component metadata');
          }
  
          aliveMessageData.commandOptions = commandOptions;
          aliveMessageData.projectPath = this.project.root;
  
          wsManager.onConnect(JSON.stringify(aliveMessageData));
          wsManager.registerServerStatus(ngServeStatus);
  
  
          if (process.env.NG_IDE_PORT) {
            wsManager.register(process.env.NG_IDE_PORT);
          } else {
            debug('Server port env part not set, falling back to port detection in locations file');
            getRunningInstallations()
              .subscribe(installations => {
                installations.forEach(installation => {
                  debug(`Installation found, connecting to port: ${installation.port}`);
                  wsManager.register(installation.port);
                });
              });
          }
  
  
          /*
          * Intercepting stdout to extract relevant info
          * e.g. Where the dev server is running, error build, etc.
          */
          intercept((text) => {
            const ngEvent = extractNGEvent(text);
  
            if (ngEvent) {
              switch (ngEvent.type) {
                case NGEventType.NG_SERVER_STATUS:
                  ngServeStatus.serverStatus = ngEvent.params.status;
                  wsManager.sendEventToAll('ng-serve-status-update');
                  break;
  
                case NGEventType.BUILD_STATUS:
                  // Resetting build error when a new build starts
                  if (ngEvent.params.status === BuildStatus.BUILD_STARTED) {
                    ngServeStatus.build.errors = [];
                  }
  
                  ngServeStatus.build.status = ngEvent.params.status;
                  wsManager.sendEventToAll('ng-serve-status-update');
                  break;
  
                case NGEventType.BUILD_ERROR:
                  ngServeStatus.build.errors.push(ngEvent.params);
                  break;
  
                default:
                // TODO: Handle unknown NGEventType
              }
            }
  
            return text;
          });
  
  
          const promise = Q.Promise(function (resolve) {
            const bootstrapScriptConfig = {
              host: '127.0.0.1',
              port: null,
            };
  
            if (process.env.NG_IDE_PORT) {
              debug(`[inject] Using env var port: ${process.env.NG_IDE_PORT}`);
  
              bootstrapScriptConfig.port = process.env.NG_IDE_PORT;
              try {
                injectCLBootstrapScript(ngCLIPackage.version, bootstrapScriptConfig);
              } catch (e) {
                console.log('[angular-ide] Error while injecting bootstrap script');
              }
              resolve(this._super.run.apply(this, [commandOptions]));
            } else {
              debug(`[inject] Environment var not set`);
              const _super = this._super;
  
              getRunningInstallations()
                .subscribe(installations => {
                  if (installations.length > 0) {
                    const sortedInstallations = installations.sort((installA, installB) => {
                      return installA.used < installB.used;
                    });
  
                    const installation = installations[0];
  
                    bootstrapScriptConfig.port = installation.port;
                    debug(`[inject-cl] Running installation detected with port ${bootstrapScriptConfig.port}`);
                    try {
                      injectCLBootstrapScript(ngCLIPackage.version, bootstrapScriptConfig);
                    } catch (e) {
                      console.log('[angular-ide] Error while injecting bootstrap script');
                    }
                    resolve(_super.run.apply(this, [commandOptions]));
                  }
                });
            }
          }.bind(this));
  
          wsManager.on('message', function (ws, data) {
            const message = JSON.parse(data);
            switch (message.command) {
              case 'terminate':
                process.exit();
                break;
              case 'injectCL':
                // Only inject CodeLive in "development" environment
                if (commandOptions.environment === 'dev' || commandOptions.target === 'development') {
                  CLEnablementStatusManager.setStatus(message.commandOptions.inject);
                  middleware.invalidate();
                }
                break;
              default:
              // TODO: Handle unknown command
            }
  
          }.bind(this));
  
          return promise;
        },
      });
  
      const CLI = require(path.normalize(path.resolve(__dirname, `../../../${ngCLIPath}/ember-cli/lib/cli/cli`)));
      CLI.prototype.originalRun = CLI.prototype.run;
  
      CLI.prototype.run = function monkeyRun(environment) {
        environment.commands['serve'] = ServeCommandPatched;
  
        return CLI.prototype.originalRun.apply(this, arguments);
      };
  
      module.exports = ServeCommandPatched;
  
    }
  }
  
  
  module.exports.overrideCore = true;
  