/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */
package org.apache.camel.component.swagger

import java.util.Locale

import com.wordnik.swagger.config.SwaggerConfig
import com.wordnik.swagger.model._
import com.wordnik.swagger.core.util.ModelUtil
import com.wordnik.swagger.core.SwaggerSpec

import org.apache.camel.model.rest.{VerbDefinition, RestDefinition}
import org.apache.camel.util.FileUtil
import org.slf4j.LoggerFactory

import scala.collection.mutable.ListBuffer
import com.wordnik.swagger.model.Parameter
import com.wordnik.swagger.model.ApiDescription
import com.wordnik.swagger.model.Operation
import com.wordnik.swagger.model.ApiListing

// to iterate Java list using for loop
import scala.collection.JavaConverters._

/**
 * A Swagger reader that reads the Camel's Rest models and builds a Swagger ApiListing models.
 */
class RestSwaggerReader {

  private val LOG = LoggerFactory.getLogger(classOf[RestSwaggerReader])

  def read(rest: RestDefinition, config: SwaggerConfig): Option[ApiListing] = {

    var resourcePath = rest.getPath
    // resource path must start with slash
    if (resourcePath == null) {
      resourcePath = ""
    }
    if (!resourcePath.startsWith("/")) {
      resourcePath = "/" + resourcePath
    }

    LOG.debug("Reading rest path: {} -> {}", resourcePath, rest)

    // create a list of apis
    val apis = new ListBuffer[ApiDescription]

    // used during gathering of apis
    val operations = new ListBuffer[Operation]
    var path: String = null

    var list = rest.getVerbs.asScala
    // must sort the verbs by uri so we group them together when an uri has multiple operations
    list = list.sorted(VerbOrdering)

    for (verb: VerbDefinition <- list) {

      if (verb.getUri != path && operations.size > 0) {
        // restart
        apis += ApiDescription(
          buildUrl(resourcePath, path),
          Some(""),
          operations.toList)
        operations.clear()
      }

      path = verb.getUri

      // the method must be in upper case
      var method = verb.asVerb().toUpperCase(Locale.US)

      // create an unique nickname using the method and paths
      var nickName = createNickname(verb.asVerb(), buildUrl(resourcePath, path))

      var responseType = verb.getOutType match {
        case e: String if e.endsWith("[]") => "List[" + e.substring(0, e.length - 2) + "]"
        case e: String => e
        case _ => "java.lang.Void"
      }

      val produces = verb.getProduces match {
        case e: String if e != "" => e.split(",").map(_.trim).toList
        case _ => List()
      }

      val consumes = verb.getConsumes match {
        case e: String if e != "" => e.split(",").map(_.trim).toList
        case _ => List()
      }

      var summary = verb.getDescriptionText
      if (summary == null) {
        summary = ""
      }

      LOG.debug("Adding operation {} {}", method, nickName)

      operations += Operation(
        method,
        summary,
        "",
        responseType,
        nickName,
        0,
        produces,
        consumes,
        List(),
        List(),
        createParameters(verb, buildUrl(resourcePath, path)),
        List(),
        None)
    }

    // add remainder
    if (operations.size > 0) {
      apis += ApiDescription(
        buildUrl(resourcePath, path),
        Some(""),
        operations.toList)
    }

    if (apis.size > 0) {

      val produces = rest.getProduces match {
        case e: String if e != "" => e.split(",").map(_.trim).toList
        case _ => List()
      }

      val consumes = rest.getConsumes match {
        case e: String if e != "" => e.split(",").map(_.trim).toList
        case _ => List()
      }

      val models = ModelUtil.modelsFromApis(apis.toList)

      LOG.debug("Adding APIs with {} models", models.size)

      var desc = rest.getDescriptionText
      if (desc == null) {
        desc = ""
      }

      Some(
        ApiListing(
          config.apiVersion,
          SwaggerSpec.version,
          config.basePath,
          resourcePath,
          produces,
          consumes,
          List(), // protocols
          List(), // authorizations
          ModelUtil.stripPackages(apis.toList),
          models,
          Option(desc))
      )
    }

    else None
  }

  def createParameters(verb: VerbDefinition, absPath : String): List[Parameter] = {
    val parameters = new ListBuffer[Parameter]

    // each {} is a parameter
    val arr = absPath.split("\\/")
    for (a <- arr) {
      if (a.startsWith("{") && a.endsWith("}")) {
        var key = a.substring(1, a.length - 1)

        parameters += Parameter(
          key,
          None,
          None,
          true,
          false,
          "string",
          AnyAllowableValues,
          "path",
          None
        )
      }
    }

    // if we have input type then its a body parameter
    if (verb.getType != null) {
      var bodyType = verb.getType
      if (bodyType.endsWith("[]")) {
        bodyType = "List[" + bodyType.substring(0, bodyType.length - 2) + "]"
      }

      parameters += Parameter(
        "body",
        None,
        None,
        true,
        false,
        bodyType,
        AnyAllowableValues,
        "body",
        None
      )
    }

    parameters.toList
  }

  def createNickname(method: String, absPath : String): String = {
    val s = method + "/" + absPath
    val arr = s.split("\\/")
    val r = arr.foldLeft("") {
      (a, b) => a + toTitleCase(sanitizeNickname(b))
    }
    // first char should be lower
    r.charAt(0).toLower + r.substring(1)
  }

  def toTitleCase(s: String): String = {
    if (s.size > 0) {
      s.charAt(0).toUpper + s.substring(1)
    } else {
      s
    }
  }

  def sanitizeNickname(s: String): String = {
    // nick name must only be alpha chars
    s.replaceAll("\\W", "")
  }

  def buildUrl(path1: String, path2: String): String = {
    val s1 = FileUtil.stripTrailingSeparator(path1)
    val s2 = FileUtil.stripLeadingSeparator(path2)
    if (s1 != null && s2 != null) {
      s1 + "/" + s2
    } else if (path1 != null) {
      path1
    } else {
      path2
    }
  }

  /**
   * To sort the rest operations
   */
  object VerbOrdering extends Ordering[VerbDefinition] {
    def compare(a:VerbDefinition, b:VerbDefinition) = {
      var u1 = ""
      if (a.getUri != null) {
        // replace { with _ which comes before a when soring by char
        u1 = a.getUri.replace("{", "_")
      }
      var u2 = ""
      if (b.getUri != null) {
        // replace { with _ which comes before a when soring by char
        u2 = b.getUri.replace("{", "_")
      }

      var num = u1.compareTo(u2)
      if (num == 0) {
        // same uri, so use http method as sorting
        num = a.asVerb().compareTo(b.asVerb())
      }
      num
    }
  }

}
