text: /********************************************************* {COPYRIGHT-TOP} ***
   * Licensed Materials - Property of IBM
   * 5725-Z22, 5725-Z63, 5725-U33, 5725-Z63
   *
   * (C) Copyright IBM Corporation 2017
   *
   * All Rights Reserved.
   * US Government Users Restricted Rights - Use, duplication or disclosure
   * restricted by GSA ADP Schedule Contract with IBM Corp.
   ********************************************************** {COPYRIGHT-END} **/
  
  // Node module: apiconnect-config-to-datapower
  
  'use strict';
  const _ = require('lodash');
  const action = require('../action');
  const util = require('../util');
  const coname = require('./coname');
  
  module.exports = exports = function(swagger, options) {
    let _files = [];
    let _protocols = {};
    let _securityDefinitions = {};
    let operationIds = {};
    let activityLog = {};
  
    function checkSwagger(swagger) {
      let name = swagger.info['x-ibm-name'] || swagger.info.name;
      if (!name)
        throw new Error('API name not defined in info.x-ibm-name or info.name!');
      swagger.info.name = name;
      if (!swagger.info.version)
        throw new Error('API version not defined!');
    }
  
    function APIDefinition() {
      checkSwagger(swagger);
      this._swagger = swagger;
      this._api = `${swagger.info.name}:${swagger.info.version}`;
      this._name = coname.api(options, swagger);
      this.name = swagger.info.name;
      this.version = swagger.info.version;
      this.basePath = swagger.basePath || '/';
      this.consumes = swagger.consumes || [];
      this.produces = swagger.produces || [];
      this.operations = {};
      if (swagger.securityDefinitions) {
        _securityDefinitions =
          Object.keys(swagger.securityDefinitions).reduce((r, sd) => {
            r[sd] = new SecurityDefinition(sd, swagger.securityDefinitions[sd]);
            return r;
          }, {});
        this.securityDefinitions = _securityDefinitions;
      }
      if (swagger.security) {
        this.securityRequirements = swagger.security.map(s => {
          // if there is no properties defined => the security is not defined
          if (Object.keys(s).length == 0) return null;
          return new SecurityRequirement(s);
        }).filter(s => s !== null);
      }
      this.paths = Object.keys(swagger.paths).reduce((r, path) => {
        let p = r[path] = new APIPath(path, swagger.paths[path]);
        this.operations = Object.keys(p.operations).reduce((r, m) => {
          r[`${m.toUpperCase()} ${path}`] = p.operations[m];
          return r;
        }, this.operations);
        return r;
      }, {});
      this.assembly = new Assembly(swagger['x-ibm-configuration'].assembly);
      this.properties = [];
      let encoded = {};
      if (swagger['x-ibm-configuration'].properties) {
        Object.keys(swagger['x-ibm-configuration'].properties).forEach(name => {
          let props = swagger['x-ibm-configuration'].properties[name];
          if (_.isString(props)) {
            this.properties.push(new APIProperty(name, props, false));
            encoded[name] = false;
          } else {
            this.properties.push(
              new APIProperty(name, props.value, props.encoded));
            encoded[name] = props.encoded;
          }
        });
      }
      if (swagger['x-ibm-configuration'].catalogs) {
        Object.keys(swagger['x-ibm-configuration'].catalogs).forEach(cat => {
          let props = swagger['x-ibm-configuration'].catalogs[cat].properties;
          Object.keys(props).forEach(name => {
            if (!encoded.hasOwnProperty(name)) {
              throw new Error(
                `Override property of ${name} not defined!`);
            }
            this.properties.push(
              new APIProperty(name, props[name], encoded[name], cat));
          });
        });
      }
      swagger.schemes.forEach(p => _protocols[p] = p);
      this._swaggerLocation = `local:///swagger/${this._name}.json`;
      _files.push({
        location: this._swaggerLocation,
        content: JSON.stringify(swagger, null, 2),
      });
  
      this.activityLog = activityLog;
    }
  
    APIDefinition.prototype.getOpertation = function(method, path) {
      return this.operations[`${method.toUpperCase()} ${path}`];
    };
  
    function APIProperty(name, value, encoded, catalog) {
      this.name = name;
      this.value = encoded ? Buffer.from(value, 'base64').toString() : value;
      this.collection = catalog || '*';
    }
  
    function SecurityDefinition(name, props) {
      this._name = coname.securityDefinition(options, swagger, name);
      Object.assign(this, props);
  
      // for basic type
      if (this['x-ibm-authentication-url'] &&
          this['x-ibm-authentication-url']['tls-profile'] &&
          this['x-ibm-authentication-url']['tls-profile'] !== ''
        ) {
        let name = this['x-ibm-authentication-url']['tls-profile'];
        this['x-ibm-authentication-url']['tls-profile'] =
          coname.tlsProfile(options, name);
      } else if (this['x-ibm-authentication-registry']) {
        this['x-ibm-authentication-registry'] =
          coname.registry(options, this['x-ibm-authentication-registry']);
      } else if (this.type === 'oauth2') {
        let provider = this['x-provider'];
        if (!provider)
          throw new Error("'x-provider' not defined in the security definition.");
        this._OAuthProviderSettingsName =
            coname.oauthProviderSettings(options, provider);
        this._flow = this.flow;
        this._scopes = Object.keys(this.scopes).join('|');
      }
  
      // for apiKey type
      if (this.type === 'apiKey') {
        if (this['x-key-type']) {
          this._keyType = this['x-key-type'] === 'clientSecret' ? 'secret' : 'id';
        } else {
          this._keyType = this.name.match(/secret/i) ? 'secret' : 'id';
        }
      }
    }
  
    let securityReqNextId = 0;
    function SecurityRequirement(props) {
      this._name =
        coname.securityRequirement(options, swagger, securityReqNextId++);
      this.securities = Object.keys(props).map(n => {
        return _securityDefinitions[n];
      });
    }
  
    function APIPath(path, props) {
      this._name = coname.path(options, swagger, path);
      this.path = path;
      this.operations = Object.keys(props).reduce((r, m) => {
        let method = m.toUpperCase();
        if (util.isValidRestMethod(m)) {
          r[method] = new APIOperation(this, method, props[m]);
        }
        if (props[m] && props[m].hasOwnProperty('operationId')) {
          let opID = props[m].operationId;
          operationIds[opID] = {'path': path, 'verb': method};
        }
        return r;
      }, {});
      if (props.parameters) {
        this.parameters = props.parameters.map(p => {
          p.required = p.required ? 'on' : 'off';
          if (!p.type) p.type = 'string';
          return p;
        });
      }
    }
  
    function APIOperation(apiPath, method, props) {
      this._name = coname.operation(options, swagger, apiPath.path, method);
      this._path = apiPath;
      this.method = method;
      this.path = apiPath.path;
      Object.assign(this, props);
      if (this.parameters) {
        this.parameters = this.parameters.map(p => {
          p.required = p.required ? 'on' : 'off';
          if (!p.type) p.type = 'object';
          return p;
        });
      }
      if (this.security) {
        if (!Array.isArray(this.security))
          throw new Error('Operation overrided security should be type of array');
        if (this.security.length === 0 ||
            (this.security.length === 1 && _.isEmpty(this.security[0])))
          this.removeSecurity = true;
        else
          this.securityRequirements = this.security.map(s => {
            return new SecurityRequirement(s);
          });
      }
    }
  
    function Assembly(props) {
      this._name = coname.assembly(options, swagger);
      this.rule = new APIRule(props.execute, 'main');
      this.catches = [];
      if (props.catch) {
        props.catch.forEach((c, i) => {
          if (c.errors && c.execute && c.execute.length > 0) {
            let rule = new APIRule(c.execute,
              'errors(' + c.errors.join('+') + ')');
            c.errors.forEach(e => {
              this.catches.push(new AssemblyCatch(e, rule));
            });
          } else if (c.default && c.default.length > 0) {
            this.defaultCatch = new APIRule(c.default, 'default catch');
          }
        });
      }
    }
  
    function AssemblyCatch(error, rule) {
      this.error = error;
      this.rule = rule;
    }
  
    function AssemblySwitchCondition(con, rule) {
      this.condition = con;
      this.rule = rule;
    }
  
    let ruleNextId = 100; //let's start from 100
    function APIRule(props, type) {
      let index = ruleNextId++;
      this._name = coname.rule(options, swagger, type, index);
      if (props.length == 0) {
        throw new Error('Assembly with no action is not supported');
      }
      this.actions = props.map(action => {
        return new AssemblyAction(type, action);
      });
    }
  
    let actionNextId = 0;
    function AssemblyAction(type, props) {
      let index = actionNextId++;
      this._type = Object.keys(props)[0];
      if (!action.exists(this._type)) {
        throw new Error(`Assembly action '${this._type}' not supported.`);
      }
      Object.assign(this, props[this._type]);
      this._name = coname.action(options, swagger, type, this._type, index);
      switch (this._type) {
        case 'invoke':
          if (this['tls-profile'])
            this['tls-profile'] = coname.tlsProfile(options, this['tls-profile']);
          if (this.password)
            this.password = new PasswordAlias(this.password);
          break;
        case 'proxy':
          if (this.password)
            this.password = new PasswordAlias(this.password);
          break;
        case 'if':
          if (this.execute && this.execute.length > 0)
            this.rule = new APIRule(this.execute, `action${index}`);
          break;
        case 'switch':
          this.conditions = [];
          this.case.forEach((c, i) => {
            if (c.condition && c.execute && c.execute.length > 0) {
              let rule = new APIRule(c.execute,
                'condition(' + c.condition + ')');
              this.conditions.push(new AssemblySwitchCondition(c.condition,
                                                               rule));
            } else if (c.otherwise && c.otherwise.length > 0) {
              this.otherwiseRule = new APIRule(c.otherwise, 'otherwise');
            }
          });
          break;
        case 'operation-switch':
          this.conditions = [];
          this.case.forEach((c, i) => {
            let opCond = '';
            if (c.operations && c.execute && c.execute.length > 0) {
              c.operations.forEach((op, i) => {
                var opType = typeof op;
                let verb = '';
                let path = '';
                if (opType === 'string') {
                  let opID = op;
                  verb = operationIds[opID].verb;
                  path = swagger.basePath + operationIds[opID].path;
                } else if (op.verb && op.path) {
                  verb = op.verb.toUpperCase();
                  path = swagger.basePath + op.path;
                } else {
                  throw new Error('unsupported operation format');
                }
                if (opCond !== '') {
                  opCond += ') || (';
                }
                opCond += 'request.verb===\'' + verb +
                     '\' && request.path===\'' + path + '\'';
              });
              if (opCond.indexOf('||') !== -1) {
                opCond = '(' + opCond + ')';
              }
              let rule = new APIRule(c.execute,
                'condition(' + opCond + ')');
              this.conditions.push(new AssemblySwitchCondition(opCond,
                                                               rule));
            }
          });
          if (this.otherwise  && this.otherwise.length > 0) {
            this.otherwiseRule = new APIRule(this.otherwise, 'otherwise');
          }
          break;
        case 'gatewayscript':
          this.location = `local:///js/${this._name}.js`;
          _files.push({
            location: this.location,
            content: this.source,
          });
          break;
        case 'xslt':
          this.location = `local:///xsl/${this._name}.xsl`;
          _files.push({
            location: this.location,
            content: this.source,
          });
          break;
        case 'map':
          this.location = `local:///map/${this._name}.json`;
          _files.push({
            location: this.location,
            content: JSON.stringify(this, null, 2),
          });
          break;
        case 'oauth-generate':
          this._settingsName =
            coname.oauthProviderSettings(options, this.provider);
          break;
        case 'activity-log':
          activityLog.on = true;
          activityLog.content = this.content;
          activityLog['error-content'] = this['error-content'];
      }
    }
  
    let nextPasswordId = 0;
    function PasswordAlias(password) {
      this._name = coname.password(options, swagger, nextPasswordId++);
      this.password = password;
    }
  
    let _api = new APIDefinition();
    let _rules = [_api.assembly.rule];
    _api.assembly.catches.forEach(c => _rules.push(c.rule));
    if (_api.assembly.defaultCatch)
      _rules.push(_api.assembly.defaultCatch);
    return {
      _catalog: `${options.catalog.organization.name}:${options.catalog.name}`,
      api: _api,
      rules: _rules,
      files: _files,
      protocols: Object.keys(_protocols),
    };
  };
  