package xmlrpc.protocol

import xmlrpc.protocol.Deserializer.Deserialized

import scala.xml.{NodeSeq, Node}
import scala.language.postfixOps
import scalaz.Scalaz._

trait CollectionTypes extends Protocol {
  import Deserializer.StringToError

  // We only support array of the same type, if an array contains elements with different
  // types, we deserialize it with case classes
  implicit def ArrayXmlrpc[T: Datatype]: Datatype[Seq[T]] = new Datatype[Seq[T]] {
    override def serialize(value: Seq[T]): Node =
      <array><data>{for {elem <- value} yield toXmlrpc(elem)}</data></array>.inValue

    override def deserialize(from: NodeSeq): Deserialized[Seq[T]] =
      from \\ "array" headOption match {
        case Some(<array><data>{array @ _*}</data></array>) =>
          (for { value <- array}
            yield fromXmlrpc[T](value)).toList.sequence[Deserialized, T]

        case _ => "Expected array structure in $from".toError.failures
      }
  }

  implicit def StructXmlrpc[T: Datatype]: Datatype[Map[String, T]] = new Datatype[Map[String, T]] {
    override def serialize(map: Map[String, T]): Node = {
      def inName(name: String): Node = <name>{name}</name>
      def inMember(elems: NodeSeq): NodeSeq = <member>{elems}</member>

      lazy val struct: NodeSeq = (for {
        (key, value) <- map
      } yield inMember(inName(key) ++ toXmlrpc(value))).reduce(_ ++ _)

      <struct>{struct}</struct>.inValue
    }

    override def deserialize(from: NodeSeq): Deserialized[Map[String, T]] =
      from \\ "struct" headOption match {
        case Some(<struct>{members @ _*}</struct>) =>
          (for { member <- members }
            yield fromXmlrpc[T](member \ "value" head) map ((member \ "name" text) -> _))
            .toList
            .sequence[Deserialized, (String, T)]
            .map(_.toMap[String, T])

        case _ => s"Expected struct in:\n$from".toError.failures
      }
  }
}