/***************************************************************************
 *   Copyright (C) 2007 by Anistratov Oleg                                 *
 *   ower86@gmail.com                                                      *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License version 2        *
 *   as published by the Free Software Foundation;                         *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 ***************************************************************************/
#include "qchatserver.h"

#include "globals.h"

#include "assert.h"

#include "message.h"
#include "msghistory.h"

QChatServer::QChatServer(QObject *parent)
 : QObject        (parent),
  AbstractChatCore(),
  m_buffer        (NULL),
  m_bufferSize    (65535),
  m_welcomeMessage("Welcome to qchat"),
  m_nextUid(101)
{
  m_newConnections = new QList<QChatServerSocket*>;
  m_buffer         = (char*)malloc(m_bufferSize);
  assert(NULL != m_buffer);

  if(!m_server.listen(QHostAddress::Any, 61100))
  {
    qWarning("[QChatServer::QChatServer]: could not start listening of incoming connections. Maybe another server is already running.");
    exit(1);
  }

  connect(&m_server, SIGNAL(newConnection()), this, SLOT(processNewConnections()));

  printf("QChat server version 0.1.1 started\n");
}

QChatServer::~QChatServer()
{
}

void QChatServer::processNewConnections()
{
  QChatServerSocket* s;
  char* buf;

  while(m_server.hasPendingConnections())
  {
    s = new QChatServerSocket(this, m_server.nextPendingConnection());

   if(s->peerAddress() != QHostAddress("127.0.0.1") && m_bannedIPs.contains(s->peerAddress()))
   {
     delete s;
     return;
   }

    connect(s, SIGNAL(disconnected()), this, SLOT(processDisconnect()));
    connect(s, SIGNAL(newDtgrm())    , this, SLOT(processNewData()));

    buf = (char*)malloc(MAX_PACKET_LEN);
    assert(NULL != buf);

    m_newConnections->append(s);
  }
}

void QChatServer::processDisconnect()
{
  QChatServerSocket* s = qobject_cast<QChatServerSocket*>(sender());
  User* usr;
  QString login;

  if(s)
  {
    usr = m_users[s];
    if(usr)
    {
      login = usr->login();
      foreach(QString ch, usr->channels())
      {
        clearParametrs();
        addParametr("Channel", ch.toUtf8());

        // TODO fill other users data too
        prepareDatagram(AbstractChatCore::DISCONNECTED, Common, usr->login(), usr->info()->compName(), usr->uid(), "Disconnected");

        foreach(QChatServerSocket* soc, m_users.keys())
          sendData(soc, usr->uid());
      }
    }

    printf("%s disconnected\n", QString("%1(%2)").arg(login).arg(s->peerAddress().toString()).toAscii().data());

    qDebug("[QChatServer::processDisconnect]: %s disconnected", s->peerAddress().toString().toAscii().data());
    delete m_users.take(s);
  }

}

void QChatServer::processNewData()
{
  User*   user;
  int     dataSize;
  uint    uid;
  bool    loggedIn;
  QString expectedLogin;
  char*   ip_port;

  m_currentSocket = qobject_cast<QChatServerSocket*>(sender());

  if(m_currentSocket)
  {
    ip_port = QString(m_currentSocket->peerAddress().toString() + ":%1").arg(m_currentSocket->peerPort()).toAscii().data();

    qDebug("[QChatServer::processNewData]: New Data from %s", ip_port);

    loggedIn = m_users.contains(m_currentSocket);

    dataSize = m_currentSocket->readDtgrm(m_buffer, m_bufferSize);

    if(dataSize < 0)
    {
      qWarning("[QChatServer::processNewData](%s): error while reading from socket", ip_port);
      return;
    }

    if(dataSize < (int)protocolLen())
    {
      qWarning("[QChatServer::processNewData](%s): dataSize < ProtocolLen (%d < %d). Ignoring this packet\n", ip_port, dataSize, AbstractChatCore::protocolLen());
      return;
    }

    switch(packetType(m_buffer))
    {
      case INSTRUCTION:
        if(m_currentSocket->peerAddress() == QHostAddress::LocalHost)
          processInstructions(dataSize);
        return;
    }

    if(!loggedIn)
    {
//       if(qstrncmp(m_buffer, programId(), programIdLen() - (protocolVersion() < 4 ? 2 : 0)))
//       {
//         qWarning("[QChatServer::processNewData](%s): [ID_ERROR!]. Ignoring this packet\n", ip_port);
//         return;
//       }

      if(packetType(m_buffer) != WANT_LOGIN)
      {
        qWarning("[QChatServer::processNewData](%s): Other than WANT_LOGIN packet from not logged in user. Ignoring this packet\n", ip_port);
        return;
      }

      expectedLogin = message(m_buffer, dataSize);

      setUserLogin(expectedLogin);

    }
    else// if(SrcIp(m_buffer) > 1)
    {
//       qDebug("[QChatServer::processNewData](%s): data from logged in user(uid=%d)", ip_port, srcIp(m_buffer));
      QString nick = userName(m_buffer, dataSize);
      uint dest_uid = destIp(m_buffer);

      if(m_users[m_currentSocket])
        uid  = m_users[m_currentSocket]->uid();

      user = m_users[m_currentSocket];

      // sending paket only if nickname in this packet corresponds to user's login
      if(user->login() == nick && uid > 100)
      {
        setInputBuffer(m_buffer, dataSize);
        fillHeader();

        QString channel_name = QString().fromUtf8(getParametr("Channel", hdr()->parametrs));

        switch(hdr()->type)
        {
          case AbstractChatCore::CONNECTED :
            m_users[m_currentSocket]->addChannel(channel_name);
            break;

          case AbstractChatCore::DISCONNECTED :
            m_users[m_currentSocket]->removeChannel(channel_name);
            break;

          case AbstractChatCore::MESSAGE :
            addMessage(channel_name, new Message(hdr()));
            break;

          case AbstractChatCore::MSGS_HISTORY_REQUEST :
            if(m_channelsHistory.contains(channel_name))
              msgsHistoryAnswer(channel_name, hdr()->src_ip,
                               m_channelsHistory[channel_name]->toByteArray(getParametr("MaxMsgsHistorySize", hdr()->parametrs).toLongLong()),
                               (AbstractChatCore::ChannelType)hdr()->chnnl_id, m_currentSocket);
               return;

          case AbstractChatCore::MSGS_NUM_REQUEST :
            if(m_channelsHistory.contains(channel_name))
              msgsHistoryAnswer(channel_name, hdr()->src_ip,
                               m_channelsHistory[channel_name]->toByteArray(getParametr("MaxMsgsHistorySize", hdr()->parametrs).toLongLong()),
                               (AbstractChatCore::ChannelType)hdr()->chnnl_id, m_currentSocket);
               return;

        }

        setOutputBuffer(m_buffer, dataSize);

        // if destination user_id is empty it means msg is broadcast
        if(dest_uid == 0)
        {
          foreach(QChatServerSocket* s, m_users.keys())
            sendData(s, uid);
        }
        else if(dest_uid == 1)
        {
          sendData(m_currentSocket, uid);
        }
        else
        {
          foreach(QChatServerSocket* s, m_users.keys())
          {
            if(m_users[s]->uid() == dest_uid)
            {
              sendData(s, uid);
              break;
            }
          }
        }
      }
      else
      {
//         qDebug("[QChatServer::processNewData](%s): !data from logged in user(uid=%d)", ip_port, srcIp(m_buffer));
        setInputBuffer(m_buffer, dataSize);
        fillHeader();

        if(hdr()->type == AbstractChatCore::CHANGE_LOGIN)
          setUserLogin(nick);
      }
    }
  }
}

QString QChatServer::checkLogin(const QString & login)
{
  if(m_bannedLogins.contains(login))
    return tr("This login is banned");

  foreach(User* u, m_users.values())
    if(login == u->login())
      return tr("This login is already in use");

  return "";
}

int QChatServer::sendData(QChatServerSocket* socket, uint src_uid)
{
 /* if(largeDtgrm())
  {
    quint32 ID = m_sender->getValidID();

    if(ID)
    {
      emit wantSendLargeData(header(), headerSize(), data(), dataSize(), addr, ID);
      setNULLdataAndHeader();
    }
  }
  else
 */
  // FIXME
  setPacketSize(outputBufferSize());
  setSrcIp(outputBuffer(), src_uid);

  int bs = socket->write(outputBuffer(), outputBufferSize());

  socket->flush();

  qDebug("[QChatServer::sendData]: dtgrm size = %d, sent = %d\n", outputBufferSize(), bs);

  return bs;
//     quint16 size = outputBufferSize() <= MAX_PACKET_LEN ? outputBufferSize() : (MAX_PACKET_LEN);

  clearParametrs();
}

uint QChatServer::login(QChatServerSocket* socket, const QString & lgn)
{
  User* user = new User;

  user->setSocket(socket);
  user->setLogin(lgn);
  user->setUid(m_nextUid++);

  m_users.insert(socket, user);

  m_newConnections->removeAll(socket);

//   strncpy(m_buffer, AbstractChatCore::programId(), programIdLen());
//   setInputBuffer(m_buffer, dataSize());
//   fillHeader();

  user->info()->setCompName(compName(m_buffer, m_bufferSize));

  return m_nextUid - 1;
}

void QChatServer::rejectLogin(QChatServerSocket* socket, const QString & reason)
{
  prepareDatagram(AbstractChatCore::LOGIN_REJECTED, socket->peerAddress().toIPv4Address(), "", "", 0, reason);
  sendData(socket);
}

void QChatServer::addMessage(const QString& channel, Message* msg)
{
  MsgHistory* history;

  if(!m_channelsHistory.contains(channel))
  {
    history = new MsgHistory();

    m_channelsHistory.insert(channel, history);
  }
  else
    history = m_channelsHistory[channel];

  history->addMsg(msg);
}

void QChatServer::msgsHistoryAnswer(const QString & ch_name_id, quint64 dest_uid, const QByteArray & msgs, AbstractChatCore::ChannelType type, QChatServerSocket* soc)
{
  addParametr("Channel"    , ch_name_id.toUtf8());
  addParametr("MsgsHistory", msgs);

  prepareDatagram(AbstractChatCore::MSGS_NUM_ANSWER,
                  dest_uid,

                  "Server",
                  "Server",
                  0,

                  "",
                  type,
                  0,
                  0);


  sendData(soc);
}

void QChatServer::msgsNumAnswer(const QString & ch_name_id, quint64 dest_uid, QChatServerSocket* soc)
{
  addParametr("Channel", ch_name_id.toUtf8());
  addParametr("MsgsNum", QString().setNum(quint32(100)).toUtf8());

  prepareDatagram(AbstractChatCore::MSGS_HISTORY_ANSWER,
                  dest_uid,

                  "Server",
                  "Server",
                  0,

                  "",
                  Common,
                  0,
                  0);


  sendData(soc);
}

void QChatServer::sendInstruction(const QString & user, const QString & instruction)
{
  prepareDatagram(INSTRUCTION,
                  1,

                  user,
                  "Server",
                  1,

                  instruction,
                  Common,
                  0,
                  0);

  QChatServerSocket socket;

//   qDebug("[QChatServer::sendKickUser]: connecting to %s:%d", addr.toString().toAscii().data(), port);

  socket.connectToHost(QHostAddress::LocalHost, 61100);

  if(socket.waitForConnected(10000))
    sendData(&socket, 1);
}

void QChatServer::kickUser(const QString & user)
{
  QMapIterator<QChatServerSocket*, User*> it(m_users);

  printf("Kicking user %s...\n", user.toLocal8Bit().data());

  while(it.hasNext())
  {
    it.next();

    if(it.value()->login() == user)
    {
      // TODO send message with reason of disconnection
      it.key()->disconnectFromHost();
      printf("%s was kicked\n", user.toLocal8Bit().data());
      return;
    }
  }

  printf("%s was not found on this server\n", user.toLocal8Bit().data());
}

void QChatServer::processInstructions(int dataSize)
{
  QString user = userName(m_buffer, dataSize);

  if(destIp(m_buffer) == 1 && srcIp(m_buffer) == 1 && packetType(m_buffer) == INSTRUCTION && compName(m_buffer, dataSize) == "Server")
  {
    if(message(m_buffer, dataSize) == "Kick")
      kickUser(user);
    else if(message(m_buffer, dataSize) == "Ban")
      banUser(user);
    else if(message(m_buffer, dataSize) == "Unban")
      unbanUser(user);
    if(message(m_buffer, dataSize) == "KickIp")
      kickIp(user);
    else if(message(m_buffer, dataSize) == "BanIp")
      banIp(user);
    else if(message(m_buffer, dataSize) == "UnbanIp")
      unbanIp(user);
    else if(message(m_buffer, dataSize) == "UnbanAll")
      m_bannedLogins.clear();
    else if(message(m_buffer, dataSize) == "UnbanAllIPs")
      m_bannedIPs.clear();
  }
}

void QChatServer::banUser(const QString& user)
{
  printf("Banning user %s...\n", user.toLocal8Bit().data());
  if(!m_bannedLogins.contains(user))
    m_bannedLogins.append(user);

  kickUser(user);
}

void QChatServer::unbanUser(const QString& user)
{
  printf("Unbanning user %s...\n", user.toLocal8Bit().data());
  m_bannedLogins.removeAll(user);
}

void QChatServer::kickIp(const QString & ip)
{
  QHostAddress addr;

  if(addr.setAddress(ip))
    foreach(QChatServerSocket* s, m_users.keys())
      if(s->peerAddress() == addr)
        s->disconnectFromHost();
}

void QChatServer::banIp(const QString & ip)
{
  QHostAddress addr;

  if(addr.setAddress(ip))
  {
    if(!m_bannedIPs.contains(addr))
      m_bannedIPs.append(addr);

    kickIp(ip);
  }
}

void QChatServer::unbanIp(const QString & ip)
{
  QHostAddress addr;

  if(addr.setAddress(ip))
     m_bannedIPs.removeAll(addr);
}

void QChatServer::setUserLogin(const QString & expectedLogin)
{
  QString error = checkLogin(expectedLogin);
  User* user = NULL;
  int uid = 0;

  if(m_users.contains(m_currentSocket))
    user = m_users[m_currentSocket];

  if(expectedLogin.isEmpty())
  {
    rejectLogin(m_currentSocket, "Expected Login is empty");
    return;
  }

  if(error.isEmpty())
  {
    if(user)
    {
      user->setLogin(expectedLogin);
      uid = user->uid();
    }
    else
      uid = login(m_currentSocket, expectedLogin);

    prepareDatagram(AbstractChatCore::LOGIN_ACCEPTED, uid, expectedLogin, "Server", uid, m_welcomeMessage);
    sendData(m_currentSocket, uid);

   printf("%s is logged in\n", QString("%1(%2)").arg(expectedLogin).arg(m_currentSocket->peerAddress().toString()).toAscii().data());
  }
  else
    rejectLogin(m_currentSocket, error);
}

