/*
 * Copyright 2016 Coursera Inc.
 *
 * 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 org.coursera.common.jsonformat

import org.coursera.common.stringkey.StringKeyFormat
import play.api.libs.json.Json
import play.api.libs.json.OFormat
import play.api.libs.json.OWrites
import play.api.libs.json.Reads
import play.api.libs.json.__

/**
 * Similar to [[TypedFormats]] but flattens `definition` into the main body:
 * {{{
 *   {
 *     "typeName": "<some name>",
 *     "definitionField1": <a field from the definition object>,
 *     "definitionField2": <another field from the definition object>
 *   }
 * }}}
 */
object FlatTypedFormats {

  def flatTypedDefinitionFormat[T: StringKeyFormat, D](typeName: T, defaultFormat: OFormat[D]):
    OFormat[D] = {

    OFormat(
      flatTypedDefinitionReads(typeName, defaultFormat),
      flatTypedDefinitionWrites(typeName, defaultFormat))
  }

  def flatTypedDefinitionReads[T: StringKeyFormat, D](typeName: T, defaultReads: Reads[D]):
    Reads[D] = {

    import JsonFormats.Implicits.ReadsPathMethods
    implicit val typeNameFormat = JsonFormats.stringKeyFormat[T]

    for {
      _ <- (__ \ "typeName").read[T].filter(_ == typeName).withRootPath
      definition <- {
        val typeNameDroppingReads = (__ \ "typeName").json.prune.withRootPath
        defaultReads.compose(typeNameDroppingReads)
      }
    } yield {
      definition
    }
  }

  def flatTypedDefinitionWrites[T: StringKeyFormat, D](typeName: T, defaultWrites: OWrites[D]):
    OWrites[D] = {

    implicit val typeNameFormat = JsonFormats.stringKeyFormat[T]
    OWrites { model: D =>
      val modelJson = defaultWrites.writes(model)

      require(!modelJson.keys.contains("typeName"), "Model cannot contain reserved field 'typeName'")
      modelJson + ("typeName" -> Json.toJson(typeName))
    }
  }

}