/** Copyright (c) 2020-2022 The Creators of Simphone

    class Contact: store data about contacts
    class Logs (DataProvider): retrieve data about console log messages
    class DataProvider (QObject): parent of Logs

    See the file COPYING.LESSER.txt for copying permission.
**/

#include "contacts.h"
#include "chatframe.h"

#include <QFile>

void DataProvider::attachModel(BaseModel * model)
{
  for (unsigned i = m_models.size(); i--;) {
    if (m_models[i] == model) return;
  }

  m_models.push_back(model);
}

void DataProvider::detachModel(BaseModel * model)
{
  for (unsigned i = m_models.size(); i--;) {
    if (m_models[i] == model) {
      m_models.erase(m_models.begin() + i);
      break;
    }
  }
}

void DataProvider::notifyChanges(bool force)
{
  if (m_changeLockCount <= 0 || force) {
    if (!force || m_changeLockCount < 0) m_changeLockCount = 0;

    if (m_hasChange) {
#ifdef DONOT_DEFINE
      for (unsigned i = m_models.size(); i--;) {
        if (m_models[i]) m_models[i]->dataChanged();
      }
#endif
      m_hasChange = false;
    }
  }
}

void DataProvider::notifyInserted(int start, int end)
{
  //if (end < 0) end = start;
  if (m_hasChange) notifyChanges(true);
  for (unsigned i = m_models.size(); i--;) {
    if (m_models[i]) m_models[i]->notifyRowsInserted(start, end);
  }
}

void DataProvider::notifyDeleted(int start, int end)
{
  //if (end < 0) end = start;
  for (unsigned i = m_models.size(); i--;) {
    if (m_models[i]) m_models[i]->notifyRowsDeleted(start, end);
  }
}

Logs * Logs::mc_logs = 0;

Logs::Logs()
  : m_size(0), m_lastIndex(0), m_diffDate(0), m_diffTime(false)
{
  m_showTime = SimParam::get("ui.console.showtime") != 0;
  m_showLevel = SimParam::get("ui.console.showlevel") != 0;
}

unsigned Logs::count()
{
  if (!m_size) m_size = unsigned(log_count_());
  return m_size;
}

QString Logs::getLogLevel(int ndx) const
{
  QString result = "";
  ++ndx; // log starts from 1 .. log_count_()
  if (ndx >= 1 && unsigned(ndx) <= log_count_()) {
    const char * loglevel = 0;
    const char * oldlevel = 0;
    if (sim_get_pointer(log_get_string_(ndx, 0, &loglevel)) && loglevel) result = loglevel;
    if (loglevel && ndx != 1 && !m_showLevel && sim_get_pointer(log_get_string_(ndx - 1, 0, &oldlevel))) {
      if (oldlevel == loglevel) result = " ";
    }
    if (checkLogDate(ndx)) result.insert(0, "\n");
  }
  return result;
}

char Logs::checkLogDate(int ndx) const
{
  if (m_lastIndex == ndx) return m_diffDate;
  simunsigned timestamp1;
  simunsigned timestamp2;
  m_lastIndex = ndx;

  simtype log = log_get_string_(m_lastIndex, &timestamp1, 0);
  if (!sim_get_pointer(log) || !timestamp1) {
    m_diffTime = false;
    return m_diffDate = 0;
  }
  if (ndx == 1) {
    m_diffTime = true;
    return m_diffDate = 'd';
  }

  char buff1[64];
  char buff2[64];
  sim_convert_time_to_string(timestamp1, buff1);

  log = log_get_string_(m_lastIndex - 1, &timestamp2, 0);
  m_diffTime = timestamp1 != timestamp2;
  if (!sim_get_pointer(log) || !timestamp2) return m_diffDate = 0;
  if (timestamp1 < timestamp2) return m_diffDate = 't';

  sim_convert_time_to_string(timestamp2, buff2);

  buff1[SIM_SIZE_TIME - 10] = 0;
  buff2[SIM_SIZE_TIME - 10] = 0;

  return m_diffDate = strcmp(buff1, buff2) != 0;
}

QString Logs::getLogLine(int ndx) const
{
  ++ndx; // log starts from 1 .. log_count_()
  if (ndx >= 1 && unsigned(ndx) <= log_count_()) {
    simtype log = log_get_string_(ndx, 0, 0);
    if (sim_get_pointer(log)) {
      QString result = sim_get_pointer(log);
      if (checkLogDate(ndx)) result.insert(0, '\n');
      return result;
    }
  }
  return "";
}

QString Logs::getLogTime(int ndx) const
{
  ++ndx; // log starts from 1 .. log_count_()
  if (ndx >= 1 && unsigned(ndx) <= log_count_()) {
    simunsigned timestamp;
    simtype log = log_get_string_(ndx, &timestamp, 0);
    if (sim_get_pointer(log) && timestamp) {
      char buffer[64];
      sim_convert_time_to_string(timestamp, buffer);
      if (!checkLogDate(ndx)) return m_diffTime || m_showTime ? buffer + SIM_SIZE_TIME - 9 : "";

      buffer[SIM_SIZE_TIME - 10] = '\n';
      return buffer + SIM_SIZE_TIME - 10;
    }
  }
  return "";
}

const char * Logs::getLogDate(int ndx)
{
  ++ndx; // log starts from 1 .. log_count_()
  if (ndx >= 1 && unsigned(ndx) <= log_count_()) {
    simunsigned timestamp;
    simtype log = log_get_string_(ndx, &timestamp, 0);
    if (sim_get_pointer(log) && timestamp) {
      static char buffer[64];
      sim_convert_time_to_string(timestamp, buffer);
      buffer[SIM_SIZE_TIME - 10] = 0;
      return buffer;
    }
  }
  return "";
}

QString Logs::getLogText(int ndx, bool textOnly)
{
  QString result;
  char buffer[64];
  const char * loglevel;
  simunsigned timestamp;
  simtype log = log_get_string_(ndx + 1, &timestamp, &loglevel);
  if (!sim_get_pointer(log)) return "";
  if (timestamp && !textOnly) {
    sim_convert_time_to_string(timestamp, buffer);
    result = buffer;
    result.append(" ");
  }
  if (loglevel) result.append(loglevel).append(" ");
  result.append(sim_get_pointer(log));
  return result;
}

void Logs::newLog()
{
  Logs * logs = getLogs();

  unsigned old = logs->m_size;
  logs->m_size = 0;
  if (logs->count() > old) {
    logs->notifyInserted(old, logs->count() - 1);
  } else if (logs->count() < old) {
    logs->notifyDeleted(logs->count(), old - 1);
  }
}

Contact::Contact(simnumber simId, int id)
  : m_id(id)
  , m_status(SIM_STATUS_OFF)
  , m_simId(simId)
  , m_editMax(0)
  , m_state(0)
  , m_rights(0)
  , m_newMessages(0)
  , m_chatFrame(0)
  , m_chatWindow(0)
  , m_avatar(0)
{
  memset(m_soundTimes, 0, sizeof(m_soundTimes));
  memset(m_dialog, 0, sizeof(m_dialog));
  for (unsigned i = 0; i < SIM_ARRAY_SIZE(m_dialog); i++) m_posY[i] = m_posX[i] = -1;
}

Contact::~Contact()
{
  delete m_avatar;
  if (m_chatFrame) {
    m_chatFrame->close();
    delete m_chatFrame;
    m_chatFrame = 0;
  }
}

void Contact::clearNotifications(E_FlagsState flags)
{
  if (m_state & flags) {
    log_xtra_("ui", "%s %X\n", __FUNCTION__, flags);
    m_state &= ~flags;
    SimCore::get()->emitContactChanged(m_id);
    Contacts::get()->unblinkTrayIcon('c');
  }
}

void Contact::setNotifications(E_FlagsState flags)
{
  m_state |= flags | flag_notify;
  SimCore::get()->emitContactChanged(m_id);
}

void Contact::setTestContact(bool test)
{
  if (test) {
    m_state |= flag_testContact;
  } else {
    m_state &= ~flag_testContact;
  }
}

void Contact::setNewContact(bool newContact)
{
  if (!newContact) {
    m_state &= ~flag_newContact;
  } else {
    m_state |= flag_newContact;
  }
}

void Contact::setMyself(bool myself)
{
  if (myself) {
    m_state |= flag_myself;
  } else {
    m_state &= ~flag_myself;
  }
}

void Contact::setSystem(bool system)
{
  if (system) {
    m_state |= flag_system;
  } else {
    m_state &= ~flag_system;
  }
}

void Contact::setForgotten(bool forgotten)
{
  if (forgotten) {
    m_state |= flag_forgotten;
  } else {
    m_state &= ~flag_forgotten;
  }
}

void Contact::setDeleted(bool deleted)
{
  if (deleted) {
    m_state |= flag_deleted;
  } else {
    m_state &= ~flag_deleted;
  }
}

void Contact::setBlocked(bool blocked)
{
  if (blocked) {
    m_state |= flag_blocked;
  } else {
    m_state &= ~flag_blocked;
  }
}

void Contact::setVerify(bool verify)
{
  if (verify) {
    m_state |= flag_verify;
  } else {
    m_state &= ~flag_verify;
  }
}

void Contact::setTypingFlag(bool typing)
{
  if (typing) {
    m_state |= flag_typing;
  } else {
    m_state &= ~flag_typing;
  }
}

void Contact::setTraversed(bool traversed)
{
  if (traversed) {
    m_state |= flag_traversed;
  } else {
    m_state &= ~flag_traversed;
  }
}

QString Contact::getNickToolTip(bool disambiguation) const
{
  QString qs;
  if (isTest()) {
    qs = disambiguation ? tr("<b>The test user</b>") : tr("<b>the test user</b>");
  } else if (isSystem()) {
    qs = disambiguation ? tr("<b>The system user</b>") : tr("<b>the system user</b>");
  } else if (isMe()) {
    qs = disambiguation ? tr("<b>You</b>") : tr("<b>you</b>");
  } else if (getDefaultNick() == m_nick) {
    qs = disambiguation ? tr("<b>This user</b>") : tr("<b>this user</b>");
  }
  return qs.isEmpty() || qs == " " ? m_nick.toHtmlEscaped() : qs;
}

QString Contact::getStateToolTip(unsigned state) const
{
  QString qs;

  if (isMe()) {
    switch (state) {
      case state_none: qs = tr("You are offline."); break;
      case state_unknown: qs = tr("You are invisible."); break;
      case state_logged: qs = tr("You are online."); break;
      case state_busy: qs = tr("You do not wish to be disturbed."); break;
      case state_hide: qs = tr("You seem to be hiding."); break;
      case state_away: qs = tr("You are away from the computer."); break;
    }
  } else {
    qs = getNickToolTip(true);
    switch (state) {
      case state_none: qs = tr("%1 seems to be offline.").arg(qs); break;
      case state_unknown: qs = tr("%1 seems NOT online.").arg(qs); break;
      case state_logged: qs = tr("%1 seems to be online.").arg(qs); break;
      case state_busy: qs = tr("%1 does not seem to wish to be disturbed.").arg(qs); break;
      case state_hide: qs = tr("%1 seems to be hiding (not seeing you).").arg(qs); break;
      case state_away: qs = tr("%1 seems to be away (not at the computer).").arg(qs); break;
      case state_new: qs = tr("%1 has requested contact with you.").arg(qs); break;
      case state_blocked: qs = tr("%1 is blocked.").arg(qs); break;
      case state_deleted: qs = tr("%1 was deleted.").arg(qs); break;
      default: qs = QString();
    }
    if (!qs.isEmpty()) qs = tr(qs.toUtf8().data());
  }

  return qs;
}

QString Contact::getAuthText() const
{
  QString text;
  if (isTest()) {
    text = tr("TEST USER");
  } else if (isMe()) {
    text = tr("YOURSELF");
  } else if (isForgotten()) {
    text = tr("FORGOTTEN");
  } else if (isDeleted()) {
    text = tr("DELETED");
  } else if (isBlocked()) {
    text = tr("BLOCKED");
  } else if (isNewContact()) {
    text = tr("UNKNOWN USER");
  } else if (!isVerified()) {
    text = tr("NOT VERIFIED");
  } else if (isSystem()) {
    text = tr("SYSTEM USER");
  }
  return text;
}

unsigned Contact::getContactState() const
{
  if (isNewContact()) return state_new;
  if (isBlocked()) return state_blocked;
  if (isDeleted()) return state_deleted;
  return getState();
}

QDialog * Contact::getDialog(int dialog, int * posX, int * posY) const
{
  if (posX) *posX = m_posX[dialog];
  if (posY) *posY = m_posY[dialog];

  return m_dialog[dialog];
}

QString Contact::findCountry(unsigned ip)
{
  QFile files[5];
  QByteArray ips[5];
  for (int i = 0; i <= 4; i++) {
    QString qs;
    files[i].setFileName(qs.sprintf(":/ip%d", i));
    if (files[i].open(QFile::ReadOnly)) ips[i] = files[i].readAll();
  }

  QStringList list;
  QFile countries(":/countries");
  if (countries.open(QFile::ReadOnly | QFile::Text)) list = QString(countries.readAll()).split('\n');

  int country = 666;
  int max = ips[0].size();

  if (ips[1].size() == max && ips[2].size() == max && ips[3].size() == max && ips[4].size() == max && max--) {
    unsigned val;
    int min = 0;
    while (min < max) {
      int i = (min + max) / 2;
      val = uchar(ips[1][i]) << 24 | uchar(ips[2][i]) << 16 | uchar(ips[3][i]) << 8 | uchar(ips[4][i]);
      if (val < ip) {
        min = i + 1;
      } else {
        max = i;
      }
    }
    val = uchar(ips[1][max]) << 24 | uchar(ips[2][max]) << 16 | uchar(ips[3][max]) << 8 | uchar(ips[4][max]);
    country = uchar(ips[0][max - (val != ip)]);
  }

  return !country-- ? tr("unknown") : country < list.size() && list[country].size() ? list[country] : tr("UNKNOWN");
}

QString Contact::convertLocationToCountry(const char * ip, int location)
{
  if (!ip) return QString();
  if (!*ip || location < 0 || location > 1) return tr("UNDEFINED");
  if (m_ip[location] == ip) return m_country[location];

  unsigned address;
  if (!convertAddress(ip, &address)) return tr("undefined");

  m_ip[location] = ip;
  return m_country[location] = findCountry(address);
}

bool Contact::convertAddress(const QString & ip, unsigned * result)
{
  QStringList list(ip.split('.'));

  *result = 0;
  if (list.size() != 4) return false;

  for (int n = 0; n < 4; n++) {
    bool ok = false;
    int i = list[n].toInt(&ok);
    if (!ok || i < 0 || i > 255) return false;
    *result = *result << 8 | i;
  }
  return true;
}

Contact::E_State Contact::convertStatusToState(short status, QString * name) const
{
  QString qs;
  E_State state;

  switch (status) {
    case SIM_STATUS_OFF: state = state_none, qs = tr("off"); break;
    case SIM_STATUS_ON: state = state_logged, qs = tr("on"); break;
    case SIM_STATUS_AWAY: state = state_away, qs = tr("idle"); break;
    case SIM_STATUS_BUSY: state = state_busy, qs = tr("busy"); break;
    case SIM_STATUS_HIDE: state = state_hide, qs = tr("hiding"); break;
    case SIM_STATUS_INVISIBLE: state = state_unknown, qs = isMe() ? tr("invisible") : tr("gone"); break;
    default: state = state_none, qs = "???";
  }

  if (name) *name = qs;
  return state;
}

QString Contact::convertTimeToString(simnumber time)
{
  QString qs;

  if (time >= 86400) {
    qs.sprintf("%d:%02d:%02d:%02d", int(time / 86400), int(time / 3600 % 24), int(time / 60 % 60), int(time % 60));
  } else if (time >= 3600) {
    qs.sprintf("%02d:%02d:%02d", int(time / 3600 % 24), int(time / 60 % 60), int(time % 60));
  } else {
    qs.sprintf("%02d:%02d", int(time / 60 % 60), int(time % 60));
  }
  return qs;
}

QString Contact::convertTimeToText(simnumber time, int units)
{
  QString qs;

  if (time >= (units == 2 ? 52 : 1) * 604800) {
    qs = tr("%n week(s)", 0, int(time / 604800));
    if (!units) qs.append(tr(", ")).append(tr("%n day(s)", 0, int(time / 86400 % 7)));
  } else if (time >= (units == 2 ? 28 : 1) * 86400) {
    qs = tr("%n day(s)", 0, int(time / 86400));
    if (!units) qs.append(tr(", ")).append(tr("%n hour(s)", 0, int(time / 3600 % 24)));
  } else if (time >= (units == 2 ? 24 : 1) * 3600) {
    qs = tr("%n hour(s)", 0, int(time / 3600));
    if (!units) qs.append(tr(", ")).append(tr("%n minute(s)", 0, int(time / 60 % 60)));
  } else if (time >= (units == 2 ? 60 : 1) * 60) {
    qs = tr("%n minute(s)", 0, int(time / 60));
    if (!units) qs.append(tr(", ")).append(tr("%n second(s)", 0, int(time % 60)));
  } else {
    qs = tr("%n second(s)", 0, int(time));
  }
  return qs;
}
