import enet, strutils, idgen, tables, math_helpers,
  estreams, sg_packets, server_utils, sg_assets, client_helpers
when appType == "gui":
  import sfml, sfml_colors, sg_gui,
    input_helpers, sfml_stuff
else:
  import times
type
  TCallback = proc(client: PClient; buffer: PBuffer)
var
  server: PHost
  dirServer: PServer
  standAloneMode = true
  event: enet.TEvent
  clientID = newIDGen[int32]()
  clients = initTable[int32, PClient](64)
  handlers = initTable[char, TCallback](32)

when appType == "gui":
  var
    gui = newGuiContainer()
    chatBox = gui.newMessageArea(vec2f(15, 550))
    window = newRenderWindow(videoMode(800, 600, 32), "Sup yo", sfDefaultSTyle)
    mousepos = newText("", guiFont, 16)
    fpsText = mousePos.copy()
    inputClient = newKeyClient(setActive = true)
  chatBox.sizeVisible = 30
  mousePos.setColor(Green)
  fpsText.setposition(vec2f(0, 20))
  inputClient.registerHandler MouseLeft, down, proc() =
    gui.click(input_helpers.getMousePos())
  inputClient.registerHandler MouseMiddle, down, proc() =
    let pos = input_helpers.getMousePos()
    mousePos.setString("($1,$2)".format(ff(pos.x), ff(pos.y)))
    mousePos.setPosition(pos)
  proc dispMessage(args: varargs[string, `$`]) =
    var s = ""
    for it in items(args):
      s.add it
    chatbox.add(s)
  proc dispError(args: varargs[string, `$`]) =
    var s = ""
    for it in items(args): s.add(it)
    chatBox.add(newScChat(kind = CError, text = s))
else:
  proc dispMessage(args: varargs[string, `$`]) =
    var m = ""
    for it in items(args): m.add(it)
    echo "<msg> ", m
  proc dispError(args: varargs[string, `$`]) =
    var m = ""
    for it in items(args): m.add(it)
    echo "**", m


var pubChatQueue = newBuffer(1024)
proc queuePub(sender: PClient, msg: CsChat) =
  var chat = newScChat(kind = CPub, fromPlayer = sender.alias, text = msg.text)
  pubChatQueue.write(HChat)
  pubChatQueue.pack(chat)
proc flushPubChat() =
  if pubChatQueue.isDirty:
    let packet = pubChatQueue.toPacket(FlagReliable)
    for id, client in pairs(clients):
      discard client.peer.send(0.cuchar, packet)
    pubChatQueue.flush()

handlers[HChat] = proc(client: PClient; buffer: PBuffer) =
  var chat = readCsChat(buffer)

  if not client.auth:
    client.sendError("You are not logged in.")
    return
  #if chat.target != "": ##private
  #  if alias2client.hasKey(chat.target):
  #    alias2client[chat.target].forwardPrivate(client, chat.text)
  #else:

  dispmessage("<", client.alias, "> ", chat.text)
  queuePub(client, chat)

handlers[HLogin] = proc(client: PClient; buffer: PBuffer) =
  var info = readCsLogin(buffer)
  if client.auth:
    client.sendError "You are already logged in."
    return
  client.alias = info.alias
  client.auth = true
  var resp = newScLogin(client.id, client.alias, "sessionkeylulz")
  client.send HLogin, resp
  client.sendMessage "welcome"
  dispMessage("Client logged in: ", client)


handlers[HFileTransfer] = server_utils.handleFilePartAck
handlers[HFileChallenge] = server_utils.handleFileChallengeResp

handlers[HZoneJoinReq] = proc(client: PClient; buffer: PBuffer) =
  var info = readCsZoneJoinReq(buffer)
  dispmessage "Got zone join request"
  client.startVerifyingFiles()



when isMainModule:
  import parseopt, matchers, os, json


  if enetInit() != 0:
    quit "Could not initialize ENet"

  var address: enet.TAddress

  block:
    var zoneCfgFile = "./server_settings.json"
    for kind, key, val in getOpt():
      case kind
      of cmdShortOption, cmdLongOption:
        case key
        of "f", "file":
          if existsFile(val):
            zoneCfgFile = val
          else:
            echo("File does not exist: ", val)
        else:
          echo("Unknown option: ", key," ", val)
      else:
        echo("Unknown option: ", key, " ", val)
    var jsonSettings = parseFile(zoneCfgFile)
    let
      port = uint16(jsonSettings["port"].num)
      zoneFile = jsonSettings["settings"].str
      dirServerInfo = jsonSettings["dirserver"]

    address.host = EnetHostAny
    address.port = port

    var path = getAppDir()/../"data"/zoneFile
    if not existsFile(path):
      echo("Zone settings file does not exist: ../data/", zoneFile)
      echo(path)
      quit(1)

    discard """block:
      var
        TestFile: FileChallengePair
        contents = repeat("abcdefghijklmnopqrstuvwxyz", 2)
      testFile.challenge = newScFileChallenge("foobar.test", FZoneCfg, contents.len.int32)
      testFile.file = checksumStr(contents)
      myAssets.add testFile"""

    setCurrentDir getAppDir().parentDir()
    let zonesettings = readFile(path)
    var
      errors: seq[string] = @[]
    if not loadSettings(zoneSettings, errors):
      echo("You have errors in your zone settings:")
      for e in errors: echo("**", e)
      quit(1)
    errors.setLen 0

    var pair: FileChallengePair
    pair.challenge.file = zoneFile
    pair.challenge.assetType = FZoneCfg
    pair.challenge.fullLen = zoneSettings.len.int32
    pair.file = checksumStr(zoneSettings)
    myAssets.add pair

    allAssets:
      if not load(asset):
        echo "Invalid or missing file ", file
      else:
        var pair: FileChallengePair
        pair.challenge.file = file
        pair.challenge.assetType = assetType
        pair.challenge.fullLen = getFileSize(
          expandPath(assetType, file)).int32
        pair.file = asset.contents
        myAssets.add pair

    echo "Zone has ", myAssets.len, " associated assets"

    dirServer = newServer()

    dirServer.addHandler HDsMsg, proc(serv: PServer; buffer: PBuffer) =
      var m = readDsMsg(buffer)
      dispMessage("<DirServer> ", m.msg)
    dirServer.addHandler HZoneLogin, proc(serv: PServer; buffer: PBuffer) =
      let loggedIn = readDsZoneLogin(buffer).status
      if loggedIn:
        #dirServerConnected = true

    if dirServerInfo.kind == JArray:
      var error: string
      if not dirServer.connect(dirServerInfo[0].str, dirServerInfo[1].num.int16, error):
        dispError("<DirServer> "&error)


  server = enet.createHost(address, 32, 2,  0,  0)
  if server == nil:
    quit "Could not create the server!"

  dispMessage("Listening on port ", address.port)

  var
    serverRunning = true
  when appType == "gui":
    var frameRate = newClock()
    var pubChatDelay = newClock()
  else:
    var frameRate = epochTime()
    var pubChatDelay = frameRate

  while serverRunning:
    when appType == "gui":
      let dt = frameRate.restart.asMilliseconds().float / 1000.0

      for event in window.filterEvents():
        case event.kind
        of sfml.EvtClosed:
          window.close()
          serverRunning = false
        else:
          discard
    else:
      let dt = epochTime() - frameRate ##is this right? probably not
      frameRate = epochTime()

    while server.hostService(event, 10) > 0:
      case event.kind
      of EvtConnect:
        var client = newClient()
        clients[client.id] = client

        event.peer.data = addr client.id
        client.peer = event.peer

        dispMessage("New client connected ", client)

        var
          msg = "hello"
          resp = createPacket(cstring(msg), msg.len + 1, FlagReliable)

        if event.peer.send(0.cuchar, resp) < 0:
          echo "FAILED"
        else:
          echo "Replied"
      of EvtReceive:
        let client = clients[cast[ptr int32](event.peer.data)[]]

        var buf = newBuffer(event.packet)
        let k = buf.readChar()
        if handlers.hasKey(k):
          handlers[k](client, buf)
        else:
          dispError("Unknown packet from ", client)

        destroy(event.packet)
      of EvtDisconnect:
        var
          id = cast[ptr int32](event.peer.data)[]
          client = clients[id]
        if client.isNil:
          disperror("CLIENT IS NIL!")
          dispmessage(event.peer.data.isNil)
        else:
          dispMessage(clients[id], " disconnected")
          GCUnref(clients[id])
          clients.del id

        event.peer.data = nil
      else:
        discard

    when appType == "gui":
      fpsText.setString(ff(1.0/dt))
      if pubChatDelay.getElapsedTime.asSeconds > 0.25:
        pubChatDelay.restart()
        flushPubChat()
    else:
      pubChatDelay -= dt
      if frameRate - pubChatDelay > 0.25:
        flushPubChat()

    when appType == "gui":
      window.clear(Black)
      window.draw(GUI)
      window.draw chatbox
      window.draw mousePos
      window.draw fpstext
      window.display()

  server.destroy()
  enetDeinit()
