package actors

import actors.UserActor.{BanMapMessage, GetName, HostLeaveLobby, LobbyFatal, Message, RefreshLobbyInfo, SecondPlayerLeaveLobby, SendBanMapMessage, SendMessage, SetUserAsHost, SetUserAsObs, SetUserAsSecondPlayer}
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,
                ip: String) extends Actor with LazyLogging {

  implicit val timeout: Timeout = 3.seconds

  val ipAddress: String = ip

  var lobbieActor: Option[ActorRef] = None

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

  implicit val selectedRacesWrites: OWrites[SelectedRaces] = Json.writes[SelectedRaces]

  implicit val userInfoWrites: OWrites[UserInfo] = Json.writes[UserInfo]

  implicit val lobbyWrites: Writes[LobbyInfo] = new Writes[LobbyInfo]{
    override def writes(lobby: LobbyInfo) = Json.obj(
      "user1Info" -> lobby.user1Info,
      "user2Info" -> lobby.user2Info,
      "lobbyActorName" -> lobby.lobbyActorName,
      "deciderName" -> lobby.deciderName,
      "status" -> lobby.status,
      "playerTurn" -> lobby.playerTurn,
      "selectedType" -> lobby.selectedType,
      "maps" -> Json.toJson(lobby.maps)
    )
  }

  implicit val messageWrites: OWrites[Message] = Json.writes[Message]

  implicit val messageBanMapWrites: OWrites[BanMapMessage] = Json.writes[BanMapMessage]

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

    override def writes(o: List[LobbyInfo]): JsValue = {
      JsArray(o.map(lobby => Json.obj(
        "user1Info" -> lobby.user1Info,
        "user2Info" -> lobby.user2Info,
        "lobbyActorName" -> lobby.lobbyActorName,
        "deciderName" -> lobby.deciderName,
        "status" -> lobby.status,
        "playerTurn" -> lobby.playerTurn,
        "selectedType" -> lobby.selectedType,
        "maps" -> Json.toJson(lobby.maps)
      )))
    }
  }

  var name = ""

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

    configureDefaultStocks()
  }

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

  override def receive: Receive = LoggingReceive {

    case GetName =>
      sender ! name

    case SetUserAsHost(lobbyInfo) =>
      val lobbyActor = sender()
      logger.info(s"Receive set user ${self.path.name} as host from ${lobbyActor.path.name}")
      setUserAsJoinedOrCreatedLobby(lobbyActor, lobbyInfo)

    case SetUserAsSecondPlayer(lobbyInfo) =>
      val lobbyActor = sender()
      logger.info(s"Receive set user ${self.path.name} as second player from ${lobbyActor.path.name}")
      setUserAsJoinedOrCreatedLobby(lobbyActor, lobbyInfo)

    case SetUserAsObs(lobbyInfo) =>
      val lobbyActor = sender()
      logger.info(s"Receive set user ${self.path.name} as second player from ${lobbyActor.path.name}")
      setUserAsJoinedOrCreatedLobby(lobbyActor, lobbyInfo)

    case HostLeaveLobby =>
      logger.info(s"Host leave from lobby ${sender.path.name}")
      out ! Json.obj("type" -> "lobbyError", "error" -> "Host leave lobby")

    case SecondPlayerLeaveLobby =>
      logger.info(s"Second player lobby ${sender.path.name}")
      out ! Json.obj("type" -> "lobbyError", "error" -> "Second player leave lobby")

    case RefreshLobbyInfo(lobbyInfo) =>
      logger.trace(s"Refresh lobby info: $lobbyInfo")
      out ! Json.obj("type" -> "refreshLobby", "lobby" -> lobbyInfo)

    case SendMessage(message) =>
      logger.debug(s"Send message $message")
      out ! Json.obj("type" -> "sendMessage", "message" -> message)

    case SendBanMapMessage(mapBanMessage) =>
      logger.debug(s"Send ban map message $mapBanMessage")
      out ! Json.obj("type" -> "sendMapBanMessage", "message" -> mapBanMessage)

    case LobbyFatal(message) => // После этого сообщения выход из лобби
      logger.warn(s"Lobby error: $message")
      out ! Json.obj("type" -> "lobbyError", "error" -> message)

    case json: JsValue =>
      // Обрабатываем запросы с фронта

      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("createDecider") =>
          val deciderName = (json \ "deciderName").as[String]
          LobbiesActor.actor ! CreateLobby(name, deciderName)

        case Some("leaveDecider") =>
          lobbieActor.foreach(lobby => lobby ! LeaveLobby)

        case Some("setReady") =>
          lobbieActor.foreach(lobby => lobby ! SetReady)

        case Some("setNotReady") =>
          lobbieActor.foreach(lobby => lobby ! SetNotReady)

        case Some("sendMessage") =>
          val message = (json \ "message").as[String]
          lobbieActor.foreach(lobby => lobby ! MessageForLobby(message))

        case Some("changeLobbyType") =>
          val lobbyType = (json \ "lobbyType").as[String]
          lobbieActor.foreach(lobby => lobby ! ChangeLobbyType(lobbyType))

        case Some("kickSecondPlayer") =>
          lobbieActor.foreach(lobby => lobby ! KickSecondPlayer)

        case Some("joinDecider") =>
          val lobbyActorName = (json \ "lobbyActorName").as[String]
          logger.info(s"Player ${self.path.name} join lobby $lobbyActorName")
          LobbiesActor.actor ! JoinLobbyByActorName(lobbyActorName, name)

        case Some("observerDecider") =>
          val lobbyActorName = (json \ "lobbyActorName").as[String]
          logger.info(s"Player ${self.path.name} observe lobby $lobbyActorName")
          LobbiesActor.actor ! ObserveLobbyByActorName(lobbyActorName)

        case Some("banMap") =>
          val map = (json \ "map").as[String]
          lobbieActor.foreach(lobby => lobby ! BanMap(map))

        case Some("selectRace") =>
          logger.info("RACES: " + json.toString())
          val races = (json \ "mainRaces").as[List[Int]]
          val additionalRaces = (json \ "additionalRaces").as[List[Int]]
          val raceTextInput = (json \ "raceTextInput").as[String]
          lobbieActor.foreach(lobby => lobby ! SelectedRaces(races, additionalRaces, raceTextInput))

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

        case Some(value) =>
          logger.error(s"Command '$value' not expected")
        case None =>
          logger.error(s"Field 'type' should be specified in incoming json'")
      }
  }

  private def setUserAsJoinedOrCreatedLobby(lobbyActor: ActorRef, lobbyInfo: LobbyInfo): Unit = {
    this.lobbieActor = Some(lobbyActor)
    out ! Json.obj("type" -> "switchToLobby", "lobby" -> lobbyInfo)
  }

}

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

  import UserParentActor._

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


object UserParentActor {

  case class Create(id: String, ipAddress: String, out: 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 class Message(userName: String, message: String)
  case class SendMessage(message: Message)

  case class BanMapMessage(user: String, mapTechName: String)
  case class SendBanMapMessage(message: BanMapMessage)

  case class RefreshLobbyInfo(lobbyInfo: LobbyInfo)

  case class SetUserAsHost(lobbyInfo: LobbyInfo)

  case class SetUserAsSecondPlayer(lobbyInfo: LobbyInfo)

  case class SetUserAsObs(lobbyInfo: LobbyInfo)

  case class LobbyFatal(message: String)

  case object CreateLobby

  case object HostLeaveLobby

  case object SecondPlayerLeaveLobby
}

