 /**
- * Copyright IBM Corporation 2016
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- **/
+* Copyright IBM Corporation 2016
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+**/
 
 import Foundation
 

  /**
  * The vcap option property is ignored if not running locally.
  */
    public init(options: [String: Any]) throws {
    // NSProcessInfo.processInfo().environment returns [String : String]
    let environmentVars = ProcessInfo.processInfo.environment
    let vcapApplication = environmentVars["VCAP_APPLICATION"]

    app = try AppEnv.parseEnvVariable(isLocal: isLocal, environmentVars: environmentVars,
      variableName: "VCAP_APPLICATION", variableType: "application", options: options)

      // Get services
      services = try AppEnv.parseEnvVariable(isLocal: isLocal, environmentVars: environmentVars,
        variableName: "VCAP_SERVICES", variableType: "services", options: options)

        // Get port
        port = try AppEnv.parsePort(environmentVars: environmentVars, app: app)

        // Get name
        var applicationJSON: [String: Any]? = [:]
        if let _ = options["vcap"] as? [String: Any]
        {
          let vcapJSON = options["vcap"] as? [String: Any]
          applicationJSON = (vcapJSON?["application"] as? [String: Any])!
        }
        name = AppEnv.parseName(app: app, options: applicationJSON!)

        // Get bind (IP address of the application instance)
        bind = app["host"] as? String ?? "0.0.0.0"

        // Get urls
        urls = AppEnv.parseURLs(isLocal: isLocal, app: app, port: port, options: applicationJSON!)
        url = urls[0]
      }

      /**
      * Returns an App object.
      */
      public func getApp() -> App? {
        // Get limits
        let limits: App.Limits
        let json_limits = app["limits"] as? [String: Int]
        if let memory = json_limits?["mem"],
        let disk = json_limits?["disk"],
        let fds = json_limits?["fds"] {
          limits = App.Limits(memory: memory, disk: disk, fds: fds)
        } else {
          return nil
        }

        // Get uris
        let uris = app["uris"] as? [String]
        // Create DateUtils instance
        let dateUtils = DateUtils()

        guard
        let name = (app["application_name"] as? String),
        let id = (app["application_id"] as? String),
        let version = (app["version"] as? String),
        let instanceId = (app["instance_id"] as? String) ,
        let instanceIndex = (app["instance_index"] as? Int),
        let port = (app["port"] as? Int),
        let startedAt: Date = dateUtils.convertStringToNSDate(dateString: app["started_at"] as? String),
        let spaceId = (app["space_id"] as? String) else {
          return nil
        }

        let startedAtTs = startedAt.timeIntervalSince1970

        // App instance should only be created if all required variables exist
        let appObj = App(id: id, name: name, uris: uris!, version: version,
          instanceId: instanceId, instanceIndex: instanceIndex,
          limits: limits, port: port, spaceId: spaceId,
          startedAtTs: startedAtTs, startedAt: startedAt)
          return appObj
        }

        /**
        * Returns all services bound to the application in a dictionary. The key in
        * the dictionary is the name of the service, while the value is a Service
        * object that contains all the properties for the service.
        */
        public func getServices() -> [String:Service] {
          var results: [String:Service] = [:]
          for (_, servs) in services {
            if let servsArray = servs as? Array<[String:Any]> {
              for service in servsArray {
                // A service must have a name and a label
                if let name: String = service["name"] as? String,
                let label: String = service["label"] as? String,
                let tags: [String] = service["tags"] as? [String],
                let plan: String = service["plan"] as? String,
                let credentials : [String: Any] = service["credentials"] as? [String: Any] {
                  results[name] =
                  Service(name: name, label: label, plan: plan, tags: tags, credentials: credentials)
                }
              }
            }
          }
          return results
        }

        /**
        * Returns a Service object with the properties for the specified Cloud Foundry
        * service. The spec parameter should be the name of the service
        * or a regex to look up the service. If there is no service that matches the
        * spec parameter, this method returns nil.
        */
        public func getService(spec: String) -> Service? {
          let services = getServices()
          if let service = services[spec] {
            return service
          }

          do {
            #if os(Linux)
            let regex = try RegularExpression(pattern: spec, options: RegularExpression.Options.caseInsensitive)
            #else
            let regex = try NSRegularExpression(pattern: spec, options: NSRegularExpression.Options.caseInsensitive)
            #endif

            for (name, serv) in services {
              let numberOfMatches = regex.numberOfMatches(in: name, options: [], range: NSMakeRange(0, name.characters.count))
              if numberOfMatches > 0 {
                return serv
              }
            }
          } catch let error as NSError {
            print("Error code: \(error.code)")
          }
          return nil
        }

        /**
        * Returns a URL generated from VCAP_SERVICES for the specified service or nil
        * if service is not found. The spec parameter should be the name of the
        * service or a regex to look up the service.
        *
        * The replacements parameter is a JSON object with the properties found in
        * Foundation's URLComponents class.
        */
        public func getServiceURL(spec: String, replacements: [String: Any]?) -> String? {
          var substitutions: [String: Any] = replacements ?? [:]
          let service = getService(spec: spec)
          guard let credentials = service?.credentials else {
            return nil
          }

          guard let url: String =
          credentials[substitutions["url"] as? String ?? "url"] as? String ?? credentials["uri"] as? String
          else {
            return nil
          }

          substitutions["url"] = nil
          guard var parsedURL = URLComponents(string: url) else {
            return nil
          }

          // Set replacements in a predefined order
          // Before, we were just iterating over the keys in the JSON object,
          // but unfortunately the order of the keys returned were different on
          // OS X and Linux, which resulted in different outcomes.
          if let user = substitutions["user"] as? String {
            parsedURL.user = user
          }
          if let password = substitutions["password"] as? String {
            parsedURL.password = password
          }
          if let port = substitutions["port"] as? Int {
            parsedURL.port = port
          }
          if let host = substitutions["host"] as? String {
            parsedURL.host = host
          }
          if let scheme = substitutions["scheme"] as? String {
            parsedURL.scheme = scheme
          }
          if let query = substitutions["query"] as? String {
            parsedURL.query = query
          }
          if let queryItems = substitutions["queryItems"] as? Array<[String: Any]> {
            var urlQueryItems: [URLQueryItem] = []
            for queryItem in queryItems {
              if let name = queryItem["name"] as? String {
                let urlQueryItem = URLQueryItem(name: name, value: queryItem["value"] as? String)
                urlQueryItems.append(urlQueryItem)
              }
            }
            if urlQueryItems.count > 0 {
              parsedURL.queryItems = urlQueryItems
            }
          }
          // These are being ignored at the moment
          // if let fragment = substitutions["fragment"].string {
          //   parsedURL.fragment = fragment
          // }
          // if let path = substitutions["path"].string {
          //   parsedURL.path = path
          // }
          return parsedURL.string
        }

        /**
        * Returns a JSON object that contains the credentials for the specified
        * Cloud Foundry service. The spec parameter should be the name of the service
        * or a regex to look up the service. If there is no service that matches the
        * spec parameter, this method returns nil. In the case there is no credentials
        * property for the specified service, an empty JSON is returned.
        */
        public func getServiceCreds(spec: String) -> [String: Any]? {
          guard let service = getService(spec: spec) else {
            return nil
          }
          if let credentials = service.credentials {
            return credentials
          } else {
            return [:]
          }
        }

        /**
        * Static method for parsing VCAP_APPLICATION and VCAP_SERVICES.
        */
        private static func parseEnvVariable(isLocal: Bool, environmentVars: [String:String],
          variableName: String, variableType: String, options: [String: Any]) throws
          -> [String: Any] {
            // If environment variable is found, then let's use it
            if let _ = environmentVars[variableName] {
              if let json = JSONUtils.convertStringToJSON(text: environmentVars[variableName]) {
                return json
              }
              throw CloudFoundryEnvError.InvalidValue("Environment variable \(variableName) is not a valid JSON string!")
            }
            // If environment variable was not found, let's query options
            if let _ = options["vcap"] as? [String: Any] {
              let typesJSON = (options["vcap"] as? [String: Any])!
              if let _ = typesJSON[variableType] as? [String: Any]
              {
                return (typesJSON[variableType] as? [String: Any])!
              }

              return [:]
            }

            return [:]
          }

          /**
          * Static method for parsing the port number.
          */
          private static func parsePort(environmentVars: [String:String], app: [String: Any]) throws -> Int {
            let portString: String = environmentVars["PORT"] ?? environmentVars["CF_INSTANCE_PORT"] ??
            environmentVars["VCAP_APP_PORT"] ?? "8090"

            // TODO: Are there any benefits in implementing logic similar to ports.getPort() (npm module)...?
            // if portString == nil {
            //   if app["name"].string == nil {
            //     portString = "8090"
            //   }
            //   //portString = "" + (ports.getPort(appEnv.name));
            //   portString = "8090"
            // }
            //let number: Int? = (portString != nil) ? Int(portString!) : nil

            if let number = Int(portString) {
              return number
            } else {
              throw CloudFoundryEnvError.InvalidValue("Invalid PORT value: \(portString)")
            }
          }

          /**
          * Static method for parsing the name for the application.
          */
          private static func parseName(app: [String: Any], options: [String: Any]) -> String? {
            guard let name: String = (options["name"] as? String ?? app["name"] as? String) else {
              return nil
            }
            // TODO: Add logic for parsing manifest.yml to get name
            // https://github.com/behrang/YamlSwift
            // http://stackoverflow.com/questions/24097826/read-and-write-data-from-text-file
            return name
          }

          /**
          * Static method for parsing the URLs for the application.
          */
          private static func parseURLs(isLocal: Bool, app: [String: Any], port: Int,
            options: [String: Any]) -> [String] {

              var uris: [String] = []
              if let _ = app["uris"] as? [String] {
                uris = (app["uris"] as? [String])!
              }
              if isLocal {
                uris = ["localhost:\(port)"]
              } else {
                if (uris.count == 0) {
                  uris = ["localhost"]
                }
              }

              var urls: [String] = []
              let scheme: String = (options["protocol"] as? String ?? (isLocal ? "http" : "https"))!
              for uri in uris {
                urls.append("\(scheme)://\(uri)");
              }

              return urls
            }
          }
