package actors

import actors.UserActor.GetName
import akka.actor._
import akka.event.LoggingReceive
import akka.pattern.ask
import akka.util.Timeout
import com.typesafe.scalalogging.LazyLogging
import play.api.libs.json._

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.DurationInt
import scala.util.{Failure, Success}

class UserActor(out: ActorRef,
                userParentActor: ActorRef,
                lobbiesActor: ActorRef) extends Actor with LazyLogging {

  implicit val timeout: Timeout = 3.seconds

  var userStatus: UserStatus = InLobby()

  implicit val lobbyResponseWrites: Writes[List[LobbyInfoResponse]] = new Writes[List[LobbyInfoResponse]] {

    implicit val lobbyWrites: OWrites[LobbyInfoResponse] = Json.writes[LobbyInfoResponse]

    override def writes(o: List[LobbyInfoResponse]): JsValue = {
      JsArray(o.map(lobby => Json.toJson(lobby)))
    }
  }

  implicit val mapJson: Writes[DeciderMap] = new Writes[DeciderMap] {
    def writes(deciderMap: DeciderMap): JsValue = {
      Json.obj(
        "map" -> deciderMap.map,
        "isBanned" -> deciderMap.isBanned
      )
    }
  }

  var name = ""

  override def preStart(): Unit = {
    super.preStart()

    configureDefaultStocks()
  }

  def configureDefaultStocks(): Unit = {
    logger.info(s"Creating user actor")
  }

  override def receive: Receive = LoggingReceive {

    case MapsUpdate(deciderMaps) =>
      val maps = deciderMaps.map(map => Json.toJson(map))
      out ! Json.obj("type" -> "maps", "deciderMaps" -> maps)
    case GetName =>
      sender ! name

    case json: JsValue =>
      // When the user types in a stock in the upper right corner, this is triggered

      val comType = (json \ "type").asOpt[String]
      comType match {
        case Some("userName") =>
          name = (json \ "name").as[String]
          logger.debug(s"Set user name: $name for actor ${this.self}")
        case Some("getAllUsers") =>
          val usersActorFuture = (userParentActor ? UserParentActor.GetAllUsers).mapTo[Iterable[ActorRef]]
          usersActorFuture.map(actorRefs => {
            logger.debug(s"There are ${actorRefs.size} users on site")
            actorRefs.map(userActorRef => userActorRef ? GetName).map(res => {
              res.onComplete {
                case Success(name) => logger.debug(s"There is $name on site")
              }
            })
          })

        case Some("createDecider") =>
          lobbiesActor ! CreateLobby(name)

        case Some("leaveDecider") =>
          lobbiesActor ! LeaveLobby(name)

        case Some("updateDecider") =>
          lobbiesActor ! BanMap("map1")

        case Some("getLobbies") =>
          logger.debug("Get all lobby request")
          (lobbiesActor ? GetAllLobbies).mapTo[List[LobbyInfoResponse]] onComplete {
            case Success(lobbies) => {
              logger.info(s"Received lobbies: $lobbies")
              out ! Json.obj("type" -> "lobbies", "lobbies" -> lobbies)
            }
            case Failure(ex) => logger.error("Received error", ex)
          }

      }
  }
}

class UserParentActor(actorSystem: ActorSystem) extends Actor with ActorLogging {

  import UserParentActor._

  override def receive: Receive = LoggingReceive {
    case Create(id, out, lobbiesActor) =>
      val child: ActorRef = actorSystem.actorOf(Props(classOf[UserActor], out, self, lobbiesActor), s"userActor-$id")
      sender() ! child
    case GetAllUsers =>
      sender() ! context.children
  }
}

class UserStatus

case class InLobby() extends UserStatus

case class NotReady() extends UserStatus

case class Ready() extends UserStatus


object UserParentActor {

  case class Create(id: String, out: ActorRef, lobbiesActor: ActorRef)

  case object GetAllUsers

}

object UserActor {

  trait Factory {
    // Corresponds to the @Assisted parameters defined in the constructor
    def apply(out: ActorRef): Actor
  }

  case object GetName

  case object CreateLobby

}

