/*
 * 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 play.api.libs.json.Format
import play.api.libs.json.JsError
import play.api.libs.json.OFormat
import play.api.libs.json.OWrites
import play.api.libs.json.Reads
import play.api.libs.json.Writes

import scala.reflect.ClassTag
import scala.reflect.classTag

object OrFormats {

  def unimplementedReads[T: ClassTag]: Reads[T] = {
    Reads(_ => JsError(s"Invoked `unimplementedReads` for ${classTag[T]}"))
  }

  def unimplementedWrites[T: ClassTag]: Writes[T] = Writes { _ =>
    throw new UnsupportedOperationException(s"Invoked `unimplementedOWrites for ${classTag[T]}")
  }

  def unimplementedOWrites[T: ClassTag]: OWrites[T] = OWrites { _ =>
    throw new UnsupportedOperationException(s"Invoked `unimplementedOWrites for ${classTag[T]}")
  }

  def unimplementedFormat[T: ClassTag]: Format[T] = Format(unimplementedReads, unimplementedWrites)

  def unimplementedOFormat[T: ClassTag]: OFormat[T] =
    OFormat(unimplementedReads[T], unimplementedOWrites[T])

  implicit class OrReads[A](reads: Reads[A]) {
    def orReads[B <: A: Reads]: Reads[A] = {
      import play.api.libs.functional.syntax._
      reads or implicitly[Reads[B]].map(b => b: A)
    }
  }

  implicit class OrWrites[A](writes: Writes[A]) {
    def orWrites[B <: A: Writes: ClassTag](implicit classTag: ClassTag[A]): Writes[A] = Writes {
      case b: B => implicitly[Writes[B]].writes(b)
      case a: A => writes.writes(a)
    }
  }

  implicit class OrOWrites[A](oWrites: OWrites[A]) {
    def orOWrites[B <: A: OWrites: ClassTag](implicit classTag: ClassTag[A]): OWrites[A] = OWrites {
      case b: B => implicitly[OWrites[B]].writes(b)
      case a: A => oWrites.writes(a)
    }
  }

  implicit class OrFormat[A](format: Format[A]) {
    def orFormat[B <: A: Format: ClassTag](implicit classTag: ClassTag[A]): Format[A] = {
      Format(format.orReads[B], format.orWrites[B])
    }
  }

  implicit class OrOFormat[A](oFormat: OFormat[A]) {
    def orOFormat[B <: A: OFormat: ClassTag](implicit classTag: ClassTag[A]): OFormat[A] = {
      OFormat(oFormat.orReads[B], oFormat.orOWrites[B])
    }
  }

}