/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * license agreements; and to You under the Apache License, version 2.0:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * This file is part of the Apache Pekko project, which was derived from Akka.
 */

/*
 * Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
 */

package org.apache.pekko.http.scaladsl.marshalling

import java.nio.CharBuffer

import org.apache.pekko
import pekko.http.scaladsl.model.MediaTypes._
import pekko.http.scaladsl.model.ContentTypes.`text/plain(UTF-8)`
import pekko.http.scaladsl.model._
import pekko.util.ByteString

trait PredefinedToEntityMarshallers extends MultipartMarshallers {

  implicit val ByteArrayMarshaller: ToEntityMarshaller[Array[Byte]] = byteArrayMarshaller(`application/octet-stream`)
  def byteArrayMarshaller(contentType: ContentType): ToEntityMarshaller[Array[Byte]] =
    Marshaller.withFixedContentType(contentType) { bytes => HttpEntity(contentType, bytes) }

  implicit val ByteStringMarshaller: ToEntityMarshaller[ByteString] = byteStringMarshaller(`application/octet-stream`)
  def byteStringMarshaller(contentType: ContentType): ToEntityMarshaller[ByteString] =
    Marshaller.withFixedContentType(contentType) { bytes => HttpEntity(contentType, bytes) }

  implicit val CharArrayMarshaller: ToEntityMarshaller[Array[Char]] = charArrayMarshaller(`text/plain`)
  def charArrayMarshaller(mediaType: MediaType.WithOpenCharset): ToEntityMarshaller[Array[Char]] =
    Marshaller.withOpenCharset(mediaType) { (value, charset) =>
      // https://github.com/apache/pekko-http/issues/300
      // ignore issues with invalid charset - use UTF-8 instead
      marshalCharArray(value, mediaType.withCharset(charset.charsetWithUtf8Failover))
    }
  def charArrayMarshaller(mediaType: MediaType.WithFixedCharset): ToEntityMarshaller[Array[Char]] =
    Marshaller.withFixedContentType(mediaType) { value => marshalCharArray(value, mediaType) }

  private def marshalCharArray(value: Array[Char], contentType: ContentType.NonBinary): HttpEntity.Strict =
    if (value.length > 0) {
      val charBuffer = CharBuffer.wrap(value)
      val byteBuffer = contentType.charset.nioCharset.encode(charBuffer)
      val array = new Array[Byte](byteBuffer.remaining())
      byteBuffer.get(array)
      HttpEntity(contentType, ByteString.fromArrayUnsafe(array))
    } else HttpEntity.Empty

  implicit val DoneMarshaller: ToEntityMarshaller[pekko.Done] =
    Marshaller.withFixedContentType(`text/plain(UTF-8)`) { done =>
      HttpEntity(`text/plain(UTF-8)`, "")
    }

  implicit val StringMarshaller: ToEntityMarshaller[String] = stringMarshaller(`text/plain`)
  def stringMarshaller(mediaType: MediaType.WithOpenCharset): ToEntityMarshaller[String] =
    Marshaller.withOpenCharset(mediaType) { (s, cs) =>
      // https://github.com/apache/pekko-http/issues/300
      // ignore issues with invalid charset - use UTF-8 instead
      HttpEntity(mediaType.withCharset(cs.charsetWithUtf8Failover), s)
    }
  def stringMarshaller(mediaType: MediaType.WithFixedCharset): ToEntityMarshaller[String] =
    Marshaller.withFixedContentType(mediaType) { s => HttpEntity(mediaType, s) }

  implicit val FormDataMarshaller: ToEntityMarshaller[FormData] =
    Marshaller.withFixedContentType(`application/x-www-form-urlencoded`)(_.toEntity)

  implicit val MessageEntityMarshaller: ToEntityMarshaller[MessageEntity] =
    Marshaller.strict { value => Marshalling.WithFixedContentType(value.contentType, () => value) }
}

object PredefinedToEntityMarshallers extends PredefinedToEntityMarshallers
