/*
 * 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.{Chunk, Trace}

import zio.http.internal.{CaseMode, CharSequenceExtensions, HeaderOps}

/**
 * Represents an immutable collection of headers. It extends HeaderExtensions
 * and has a ton of powerful operators that can be used to add, remove and
 * modify headers.
 *
 * NOTE: Generic operators that are not specific to `Headers` should not be
 * defined here. A better place would be one of the traits extended by
 * `HeaderExtension`.
 */

sealed trait Headers extends HeaderOps[Headers] with Iterable[Header] {
  self =>
  final def ++(other: Headers): Headers = self.combine(other)

  final def combine(other: Headers): Headers =
    Headers.Concat(self, other)

  final def combineIf(cond: Boolean)(other: Headers): Headers =
    if (cond) self ++ other else self

  def contains(key: CharSequence): Boolean

  final def get(key: CharSequence): Option[String] = Option(getUnsafe(key))

  final def get(headerType: Header.HeaderType): Option[headerType.HeaderValue] = header(headerType)

  final def getAll(headerType: Header.HeaderType): Chunk[headerType.HeaderValue] = headers(headerType)

  /**
   * @return
   *   null if header is not found
   */
  private[http] def getUnsafe(key: CharSequence): String

  override final def headers: Headers = self

  override def iterator: Iterator[Header]

  override def toString: String = mkString("Headers(", ", ", ")")

  final def modify(f: Header => Header): Headers = Headers.FromIterable(self.map(f))

  override final def updateHeaders(update: Headers => Headers)(implicit trace: Trace): Headers = update(self)

  final def when(cond: Boolean): Headers = if (cond) self else Headers.Empty

}

object Headers {

  private[zio] final case class FromIterable(iter: Iterable[Header]) extends Headers {
    self =>

    override def contains(key: CharSequence): Boolean =
      iter.exists { entry =>
        CharSequenceExtensions.equals(entry.headerName, key, CaseMode.Insensitive)
      }

    override def iterator: Iterator[Header] =
      iter.iterator

    private[http] override def getUnsafe(key: CharSequence): String = {
      val it             = iter.iterator
      var result: String = null
      while (it.hasNext && (result eq null)) {
        val entry = it.next()
        if (CharSequenceExtensions.equals(entry.headerName, key, CaseMode.Insensitive)) {
          result = entry.renderedValue
        }
      }

      result
    }
  }

  private[zio] final case class Native[T](
    value: T,
    iterate: T => Iterator[Header],
    unsafeGet: (T, CharSequence) => String,
    getAll: (T, CharSequence) => Chunk[String],
    contains: (T, CharSequence) => Boolean,
  ) extends Headers {
    override def contains(key: CharSequence): Boolean = contains(value, key)

    override def iterator: Iterator[Header] = iterate(value)

    override private[http] def getUnsafe(key: CharSequence): String = unsafeGet(value, key)

    override def rawHeaders(name: CharSequence): Chunk[String] = getAll(value, name)

  }

  private[zio] final case class Concat(first: Headers, second: Headers) extends Headers {
    self =>

    override def contains(key: CharSequence): Boolean =
      first.contains(key) || second.contains(key)

    override def iterator: Iterator[Header] =
      first.iterator ++ second.iterator

    private[http] override def getUnsafe(key: CharSequence): String = {
      val fromSecond = second.getUnsafe(key)
      if (fromSecond ne null) fromSecond else first.getUnsafe(key)
    }
  }

  private[zio] case object Empty extends Headers {

    override def contains(key: CharSequence): Boolean = false

    override def iterator: Iterator[Header] =
      Iterator.empty

    private[http] override def getUnsafe(key: CharSequence): String = null
  }

  def apply(name: CharSequence, value: CharSequence): Headers = Headers.FromIterable(Chunk(Header.Custom(name, value)))

  def apply(tuple2: (CharSequence, CharSequence)): Headers = apply(tuple2._1, tuple2._2)

  def apply(value: (CharSequence, CharSequence), values: (CharSequence, CharSequence)*): Headers =
    Headers.FromIterable((value +: values).map { case (k, v) => Header.Custom(k, v) })

  def apply(headers: Header*): Headers = FromIterable(headers)

  def apply(iter: Iterable[Header]): Headers = FromIterable(iter)

  def empty: Headers = Empty

  def fromIterable(iter: Iterable[Header]): Headers = FromIterable(iter)

  def ifThenElse(cond: Boolean)(onTrue: => Headers, onFalse: => Headers): Headers = if (cond) onTrue else onFalse

  def when(cond: Boolean)(headers: => Headers): Headers = if (cond) headers else Empty

}
