package com.wavesplatform.dex.api.http.entities

import com.fasterxml.jackson.core.{JsonGenerator, JsonParser}
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import com.fasterxml.jackson.databind.{DeserializationContext, JsonNode, ObjectMapper, SerializerProvider}
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
import com.wavesplatform.dex.domain.asset.Asset.{IssuedAsset, Waves}
import com.wavesplatform.dex.domain.asset.{Asset, AssetPair}
import com.wavesplatform.dex.domain.bytes.ByteStr
import com.wavesplatform.dex.domain.model.Denormalization
import com.wavesplatform.dex.model.LevelAgg

@JsonSerialize(using = classOf[HttpOrderBook.Serializer])
case class HttpOrderBook(timestamp: Long, pair: AssetPair, bids: Seq[LevelAgg], asks: Seq[LevelAgg], assetPairDecimals: Option[(Int, Int)] = None)

object HttpOrderBook {

  private val coreTypeSerializers = new SimpleModule()
  coreTypeSerializers.addDeserializer(classOf[AssetPair], new AssetPairDeserializer)

  private val mapper = new ObjectMapper() with ScalaObjectMapper
  mapper.registerModule(DefaultScalaModule)
  mapper.registerModule(coreTypeSerializers)

  private def serialize(value: Any): String = mapper.writeValueAsString(value)

  private class AssetPairDeserializer extends StdDeserializer[AssetPair](classOf[AssetPair]) {

    override def deserialize(p: JsonParser, ctxt: DeserializationContext): AssetPair = {
      val node = p.getCodec.readTree[JsonNode](p)

      def readAssetId(fieldName: String): Asset = {
        val x = node.get(fieldName).asText(Asset.WavesName)
        if (x == Asset.WavesName) Waves else IssuedAsset(ByteStr.decodeBase58(x).get)
      }

      AssetPair(readAssetId("amountAsset"), readAssetId("priceAsset"))
    }
  }

  private def formatValue(value: BigDecimal, decimals: Int): String = new java.text.DecimalFormat(s"0.${"0" * decimals}").format(value)

  private def denormalizeAndSerializeSide(side: Seq[LevelAgg], amountAssetDecimals: Int, priceAssetDecimals: Int, jg: JsonGenerator): Unit = {
    side.foreach { levelAgg =>
      val denormalizedPrice  = Denormalization.denormalizePrice(levelAgg.price, amountAssetDecimals, priceAssetDecimals)
      val denormalizedAmount = Denormalization.denormalizeAmountAndFee(levelAgg.amount, amountAssetDecimals)

      jg.writeStartArray(2)
      jg.writeString(formatValue(denormalizedPrice, priceAssetDecimals))
      jg.writeString(formatValue(denormalizedAmount, amountAssetDecimals))
      jg.writeEndArray()
    }
  }

  def toJson(x: HttpOrderBook): String = serialize(x)

  class Serializer extends StdSerializer[HttpOrderBook](classOf[HttpOrderBook]) {
    override def serialize(x: HttpOrderBook, j: JsonGenerator, serializerProvider: SerializerProvider): Unit = {
      j.writeStartObject()
      j.writeNumberField("timestamp", x.timestamp)

      x.assetPairDecimals.fold {

        j.writeFieldName("pair")
        j.writeStartObject()
        j.writeStringField("amountAsset", x.pair.amountAssetStr)
        j.writeStringField("priceAsset", x.pair.priceAssetStr)
        j.writeEndObject()

        j.writeArrayFieldStart("bids")
        x.bids.foreach(j.writeObject)
        j.writeEndArray()

        j.writeArrayFieldStart("asks")
        x.asks.foreach(j.writeObject)
        j.writeEndArray()

      } {
        case (amountAssetDecimals, priceAssetDecimals) =>
          j.writeArrayFieldStart("bids")
          denormalizeAndSerializeSide(x.bids, amountAssetDecimals, priceAssetDecimals, j)
          j.writeEndArray()

          j.writeArrayFieldStart("asks")
          denormalizeAndSerializeSide(x.asks, amountAssetDecimals, priceAssetDecimals, j)
          j.writeEndArray()
      }

      j.writeEndObject()
    }
  }
}