text: var yaml = require('js-yaml'),
    fs = require('fs'),
    _ = require('lodash'),
    path = require('path-browserify'),
    resolver = require('oas-resolver-browser'),
    yamlParse = require('yaml');
  
  module.exports = {
  
    asJson: function (spec) {
      try {
        return JSON.parse(spec);
      }
      catch (jsonException) {
        throw new SyntaxError(`Specification is not a valid JSON. ${jsonException}`);
      }
    },
  
    asYaml: function (spec) {
      try {
        let obj = yaml.safeLoad(spec);
        // yaml.safeLoad does not throw errors for most of the cases in invalid yaml
        // hence check if it returned an object
        if (typeof obj !== 'object') {
          throw new Error('');
        }
        return obj;
      }
      catch (yamlException) {
        throw new SyntaxError(`Specification is not a valid YAML. ${yamlException}`);
      }
    },
  
    /**
     * Validate Spec to check if some of the required fields are present.
     *
     * @param {Object} spec OpenAPI spec
     * @return {Object} Validation result
     */
    validateSpec: function (spec) {
  
      // Checking for the all the required properties in the specification
      if (!spec.hasOwnProperty('openapi')) {
        return {
          result: false,
          reason: 'Specification must contain a semantic version number of the OAS specification'
        };
      }
  
      if (!spec.hasOwnProperty('paths')) {
        return {
          result: false,
          reason: 'Specification must contain Paths Object for the available operational paths'
        };
      }
  
      if (!spec.hasOwnProperty('info')) {
        return {
          result: false,
          reason: 'Specification must contain an Info Object for the meta-data of the API'
        };
      }
      if (!spec.info.hasOwnProperty('$ref')) {
        if (!spec.info.hasOwnProperty('title')) {
          return {
            result: false,
            reason: 'Specification must contain a title in order to generate a collection'
          };
        }
  
        if (!spec.info.hasOwnProperty('version')) {
          return {
            result: false,
            reason: 'Specification must contain a semantic version number of the API in the Info Object'
          };
        }
      }
  
      // Valid specification
      return {
        result: true,
        openapi: spec
      };
    },
  
    /** Converts OpenAPI input to OpenAPI Object
     * @param {String} openApiSpec OpenAPI input in string
     * @returns {Object} oasObject
     */
    getOasObject: function (openApiSpec) {
      let oasObject = openApiSpec,
        detailedError = 'Invalid format. Input must be in YAML or JSON format.';
      try {
        oasObject = this.asYaml(openApiSpec);
      }
      catch (yamlException) {
        // Not valid YAML, could be a JSON as well
        try {
          oasObject = this.asJson(openApiSpec);
        }
        catch (jsonException) {
          // It's neither JSON nor YAML
          // try and determine json-ness or yaml-ness
          if (openApiSpec && openApiSpec[0] === '{') {
            // probably JSON
            detailedError += ' ' + jsonException.message;
          }
          else if (openApiSpec && openApiSpec.indexOf('openapi:') === 0) {
            // probably YAML
            detailedError += ' ' + yamlException.message;
          }
          return {
            result: false,
            reason: detailedError
          };
        }
      }
  
      return {
        result: true,
        oasObject
      };
    },
  
    /** Given an array of files returns the root OAS file if present
     *
     * @param {Array} filesPathArray Array of file paths
     * @param {Object} files Files map
     * @return {String} rootFile
     */
    getRootFiles: function (filesPathArray, files = {}) {
      let rootFilesArray = [];
      filesPathArray.forEach((filePath) => {
        let obj,
          file,
          oasObject;
        try {
          // Use files map if present to read files.
          if (!_.isEmpty(files)) {
            file = files[path.resolve(filePath.fileName)];
          }
          else {
            file = fs.readFileSync(filePath.fileName, 'utf8');
          }
  
          obj = this.getOasObject(file);
  
          if (obj.result) {
            oasObject = obj.oasObject;
          }
          else {
            throw new Error(obj.reason);
          }
          if (this.validateSpec(oasObject).result) {
            rootFilesArray.push(filePath.fileName);
          }
        }
        catch (e) {
          throw new Error(e.message);
        }
      });
      return rootFilesArray;
    },
  
    /**
     * Resolve file references and generate single OAS Object
     *
     * @param {Object} openapi OpenAPI
     * @param {Object} options options
     * @param {Object} files Map of files path and content
     * @return {Object} Resolved content
     */
    resolveContent: function (openapi, options, files) {
      return resolver.resolve(openapi, options.source, {
        options: Object.assign({}, options),
        resolve: true,
        cache: [],
        externals: [],
        externalRefs: {},
        rewriteRefs: true,
        openapi: openapi,
        files: files
      });
    },
  
    /** Resolves all OpenAPI file references and returns a single OAS Object
     *
     * @param {Object} source Root file path
     * @param {Object} options Configurable options as per oas-resolver module.
     * @param {Object} files Map of files path and content
     * @return {Object} Resolved OpenAPI Schema
     */
    mergeFiles: function(source, options = {}, files = {}) {
      options.source = source;
      options.origin = source;
  
      // Use files map if present instead of reading files.
      if (!_.isEmpty(files)) {
        let content = files[path.resolve(source)],
          unresolved;
        try {
          unresolved = yamlParse.parse(content, { prettyErrors: true });
        }
        // Used to indicate to users where is the issue with their API
        catch (err) {
          throw new Error('\nLine: ' + err.linePos.start.line + ', col: ' +
            err.linePos.start.col + ' ' + err.message);
        }
        return new Promise((resolve, reject) => {
          return this.resolveContent(unresolved, options, files)
            .then((result) => {
              return resolve(result.openapi);
            }, (err) => {
              return reject(err);
            });
        });
      }
      return this.readFileAsync(source, 'utf8')
        .then((content) => {
          try {
            return yamlParse.parse(content, { prettyErrors: true });
          }
          catch (err) {
            throw new Error('\nLine: ' + err.linePos.start.line + ', col: ' +
             err.linePos.start.col + ' ' + err.message);
          }
        }, (err) => {
          throw new Error(err.message);
        })
        .then((unresolved) => {
          if (options.resolve === true) {
            return this.resolveContent(unresolved, options);
          }
        }, (err) => {
          throw err;
        })
        .then((result) => {
          return result.openapi;
        }, (err) => {
          throw err;
        });
    },
  
    /** Read File asynchronously
     *
     * @param {String} filePath Path of the file.
     * @param {String} encoding encoding
     * @return {String} Contents of the file
     */
    readFileAsync: function(filePath, encoding) {
      return new Promise((resolve, reject) => {
        fs.readFile(filePath, encoding, (err, data) => {
          if (err) { reject(err); }
          else { resolve(data); }
        });
      });
    }
  };
  