/*
 * Copyright 2021 - 2023 Sporta Technologies PVT LTD & the ZIO HTTP contributors.
 *
 * 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.
 */

package zio.http

import zio.http.codec.PathCodec

/**
 * Represents an HTTP method, such as GET, PUT, POST, or DELETE.
 */
sealed trait Method { self: Product =>

  /**
   * A right-biased way of combining two methods. If either method is default,
   * the other will be returned. Otherwise, the right method will be returned.
   */
  def ++(that: Method): Method =
    if (that == Method.ANY) self
    else that

  def #|(that: Method): Method =
    if (self == Method.ANY || that == Method.ANY) Method.ANY
    else
      (self, that) match {
        case (Method.Methods(a), Method.Methods(b)) => Method.Methods(a ++ b)
        case (Method.Methods(a), _)                 => Method.Methods(a + that)
        case (_, Method.Methods(b))                 => Method.Methods(b + self)
        case _ if self == that                      => self
        case _                                      => Method.Methods(Set(self, that))
      }

  def /[A](that: PathCodec[A]): RoutePattern[A] =
    if (that == PathCodec.empty) RoutePattern.fromMethod(self).asInstanceOf[RoutePattern[A]]
    else RoutePattern.fromMethod(self) / that

  def matches(that: Method): Boolean =
    if (self == Method.ANY) true
    else if (that == Method.ANY) true
    else
      self match {
        case Method.Methods(methods) => methods.exists(_.matches(that))
        case _                       =>
          that match {
            case Method.Methods(methods) => methods.exists(_.matches(self))
            case _                       => self == that
          }
      }

  /**
   * The name of the method, as it appears in the HTTP request.
   */
  val name: String

  def render: String = if (self == Method.ANY) "*" else self.name

  override def toString: String = render
}

object Method {
  private[http] val standardMethods: Set[Method] =
    Set(OPTIONS, GET, HEAD, POST, PUT, PATCH, DELETE, TRACE, CONNECT)

  def fromString(method: String): Method =
    method.toUpperCase match {
      case POST.name    => Method.POST
      case GET.name     => Method.GET
      case OPTIONS.name => Method.OPTIONS
      case HEAD.name    => Method.HEAD
      case PUT.name     => Method.PUT
      case PATCH.name   => Method.PATCH
      case DELETE.name  => Method.DELETE
      case TRACE.name   => Method.TRACE
      case CONNECT.name => Method.CONNECT
      case _            => Method.CUSTOM(method)
    }

  final case class CUSTOM(name: String) extends Method

  final case class Methods(methods: Set[Method]) extends Method {
    val name: String = methods.toList.sortBy(_.render).map(_.render).mkString("#|")
  }

  case object OPTIONS extends Method { val name = "OPTIONS" }
  case object GET     extends Method { val name = "GET"     }
  case object HEAD    extends Method { val name = "HEAD"    }
  case object POST    extends Method { val name = "POST"    }
  case object PUT     extends Method { val name = "PUT"     }
  case object PATCH   extends Method { val name = "PATCH"   }
  case object DELETE  extends Method { val name = "DELETE"  }
  case object TRACE   extends Method { val name = "TRACE"   }
  case object CONNECT extends Method { val name = "CONNECT" }

  case object ANY extends Method { val name = "GET" }
}
