package actors

import actors.UserActor._
import akka.actor.{Actor, ActorRef}
import akka.event.LoggingReceive
import akka.util.Timeout
import com.typesafe.config.ConfigFactory
import com.typesafe.scalalogging.LazyLogging

import java.util
import scala.collection.JavaConverters.iterableAsScalaIterableConverter
import scala.collection.immutable.HashSet
import scala.concurrent.duration._
import scala.util.Try

case class LobbyUser(name: String, actorRef: ActorRef)

case class DeciderMap(map: String, description: Option[String] = None, var isBanned: Boolean = false)

/**
 * There is one StockActor per stock symbol.  The StockActor maintains a list of users watching the stock and the stock
 * values.  Each StockActor updates a rolling dataset of randomly generated stock values.
 */
class LobbieActor(hostUser: LobbyUser, deciderName: String) extends Actor with LazyLogging {

  val config = ConfigFactory.load()
  logger.info(s"Create lobby... host: ${hostUser.actorRef.path.name}")


  private val host: LobbyUser = hostUser
  private var secondPlayer: Option[LobbyUser] = None

  private var firstPlayerReady: Boolean = false
  private var secondPlayerReady: Boolean = false

  private var playerTurn = 1

  // all lobby users actors (observer and players)
  protected[this] var users: HashSet[ActorRef] = HashSet.empty[ActorRef]

  users = users + host.actorRef

  private var status: LobbyStatus = NotStarted()

  private var lobbyType: LobbyType = Last1()

  private var firstPlayerSelectedRaces: Option[SelectedRaces] = None
  private var secondPlayerSelectedRaces: Option[SelectedRaces] = None


  private val mapsLobby: Set[DeciderMap] = {
    val configMaps = config.getList(s"deciders.$deciderName.maps").asScala
    configMaps.map(e => {
      val mapConfig = e.unwrapped().asInstanceOf[util.ArrayList[String]].asScala
      DeciderMap(mapConfig.head, mapConfig.tail.headOption)
    }).toSet
  }

  hostUser.actorRef.tell(
    SetUserAsHost(LobbyInfo(
      UserInfo(hostUser.name, firstPlayerReady),
      UserInfo("", secondPlayerReady),
      self.path.name,
      deciderName,
      status.toString(),
      playerTurn,
      lobbyType.toString(),
      mapsLobby)),
    this.self)

  implicit val timeout: Timeout = 1.second


  def receive = LoggingReceive {
    case BanMap(mapName: String) =>
      // notify watchers
      logger.info(s"Ban map $mapName by ${sender.path.name}")

      if (status == Draft()) {
        if ((playerTurn == 1 && sender == host.actorRef) ||
          (playerTurn == 2 && secondPlayer.exists(_.actorRef == sender))) {
          mapsLobby.find(p => p.map == mapName) match {
            case Some(map) =>
              if (!map.isBanned) {
                map.isBanned = true
                if (playerTurn == 1) playerTurn = 2 else playerTurn = 1
                if (isFinish) {
                  status = Finish()
                }
                refreshAndBanMap(mapName)
              } else {
                logger.warn(s"User ban map $mapName, but map already banned")
              }
            case None =>
              logger.error(s"Map $mapName not exist")
          }
        } else {
          logger.warn(s"Player ${sender.path.name} ban map, but turn is $playerTurn")
        }
      } else {
        logger.warn(s"Player ${sender.path.name} ban map, but lobby status is '$status' status")
      }
    case selectedRaces: SelectedRaces =>
      if (sender == host.actorRef) {
        firstPlayerSelectedRaces = Some(selectedRaces)
        logger.info(s"First player select $selectedRaces")
      } else  if (sender == secondPlayer.get.actorRef) {
        secondPlayerSelectedRaces = Some(selectedRaces)
        logger.info(s"Second player select $selectedRaces")
      }
      if (firstPlayerSelectedRaces.nonEmpty && secondPlayerSelectedRaces.nonEmpty) {
        status = Draft()
        users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse))
      }
    case SetReady =>
      // notify watchers
      var readyPlayer = sender()
      if (readyPlayer == hostUser.actorRef) {
        firstPlayerReady = true
      } else if (secondPlayer.exists(sp => sp.actorRef == readyPlayer)) {
        secondPlayerReady = true
      }

      if (firstPlayerReady && secondPlayerReady) {
        status = SelectRace()
      }

      users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse))

    case SetNotReady =>
      // notify watchers
      var notReadyPlayer = sender()
      if (notReadyPlayer == hostUser.actorRef) {
        firstPlayerReady = false
      } else if (secondPlayer.exists(sp => sp.actorRef == notReadyPlayer)) {
        secondPlayerReady = false
      }
      users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse))

    case ChangeLobbyType(lobbyTypeNew) =>
      lobbyTypeNew match {
        case "last1" =>
          lobbyType = Last1()
        case "last3" =>
          lobbyType = Last3()
        case "last5" =>
          lobbyType = Last5()
        case "last7" =>
          lobbyType = Last7()

      }
      users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse))

    case KickSecondPlayer =>
      if (sender() == host.actorRef) {
        secondPlayer.foreach(player => player.actorRef ! LobbyFatal("You were kicked from lobby!"))
        secondPlayerReady = false
        secondPlayer = None
        users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse))
      }

    case MessageForLobby(message) =>
      if (sender() == host.actorRef) {
        users.foreach(_ ! SendMessage(Message(host.name, message)))
      } else if (secondPlayer.exists(sp => sp.actorRef == sender())) {
        users.foreach(_ ! SendMessage(Message(secondPlayer.get.name, message)))
      }
    case JoinLobbyAsPlayer(sp) =>
      logger.info(s"User ${sender.path.name} join lobby ${self.path.name} as player")
      if (secondPlayer.isEmpty) {
        secondPlayer = Some(sp)
        users = users + sp.actorRef
        sp.actorRef.tell(
          SetUserAsSecondPlayer(getLobbyInfoResponse), this.self)
        users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse))
      } else {
        sp.actorRef ! LobbyFatal("Lobby already full")
      }

    case WatchLobby(_) =>
      // add the watcher to the list
      users = users + sender
      sender ! SetUserAsObs(getLobbyInfoResponse)
    case LeaveLobby =>
      users = users - sender
      if (host.actorRef == sender()) {
        users.foreach(_ ! HostLeaveLobby)
        context.stop(self)
      } else if (secondPlayer.exists(_.actorRef == sender())) {
        secondPlayerReady = false
        secondPlayer = None
        if (status == Draft() || status == SelectRace()) {
          users.foreach(_ ! SecondPlayerLeaveLobby)
          context.stop(self)
        } else {
          users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse))
        }
      }
      if (users.isEmpty) {
        logger.info(s"Stop lobby ${self.path.name}")
        context.stop(self)
      }
    case InfoQuery =>
      logger.info(s"Info query for lobby ${self.path.name} and host ${host.actorRef.path.name}")
      sender ! RefreshLobbyInfo(getLobbyInfoResponse)
  }

  private def getLobbyInfoResponse: LobbyInfo = {
    val user2Name = secondPlayer.map(_.name).getOrElse("")
    LobbyInfo(
      UserInfo(host.name, firstPlayerReady, firstPlayerSelectedRaces),
      UserInfo(user2Name, secondPlayerReady, secondPlayerSelectedRaces),
      self.path.name,
      deciderName,
      status.toString(),
      playerTurn,
      lobbyType.toString(),
      mapsLobby)
  }

  private def isFinish: Boolean = {
    mapsLobby.count(_.isBanned == false) == 1 && lobbyType == Last1() ||
      mapsLobby.count(_.isBanned == false) == 3 && lobbyType == Last3() ||
      mapsLobby.count(_.isBanned == false) == 5 && lobbyType == Last5() ||
      mapsLobby.count(_.isBanned == false) == 7 && lobbyType == Last7()
  }

  private def refreshAndBanMap(mapName: String): Unit = {
    val senderName: String = if (sender == host.actorRef) {
      host.name
    } else secondPlayer.get.name
    users.foreach(_ ! SendBanMapMessage(BanMapMessage(senderName, mapName)))
    users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse))
  }

}

case class BanMap(mapName: String)

case class MapsUpdate(maps: Set[String])

case class CreateLobby(userName: String, deciderName: String)

case class JoinLobbyAsPlayer(lobbyUser: LobbyUser)

case object KickSecondPlayer

case class WatchLobby(lobbyName: String)

case object LeaveLobby

case object SetReady

case object SetNotReady

case class MessageForLobby(message: String)

case class ChangeLobbyType(lobbyType: String)

case class ChangeIsNecronSelected(isSelected: Boolean)

case object InfoQuery

case class SelectedRaces(mainRaces: List[Int], additionalRaces: List[Int], raceTextInput: String)

case class UserInfo(name: String, isReady: Boolean, selectedRaces: Option[SelectedRaces] = None, selectedRacesText: Option[String] = None)

case class LobbyInfo(user1Info: UserInfo,
                     user2Info: UserInfo,
                     lobbyActorName: String,
                     deciderName: String,
                     status: String,
                     playerTurn: BigDecimal,
                     selectedType: String,
                     maps: Set[DeciderMap])

class LobbyStatus

sealed case class NotStarted() extends LobbyStatus

sealed case class SelectRace() extends LobbyStatus

sealed case class Draft() extends LobbyStatus

sealed case class Finish() extends LobbyStatus

class LobbyType

sealed case class Last1() extends LobbyType

sealed case class Last3() extends LobbyType

sealed case class Last5() extends LobbyType

sealed case class Last7() extends LobbyType

sealed case class Superfinal() extends LobbyType
