/***************************************************************************
 *   Copyright (C) 2007 by Anistratov Oleg                                 *
 *   ower@users.sourceforge.net                                            *
 *                                                                         *
 *   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 "chattextwgt.h"

// TODO remove "globals.h" from this header
#include "globals.h"

#include <QTextCursor>
#include <QScrollBar>
#include <QFile>
#include <QDateTime>
#include <QTextFrame>

#include "message.h"
#include "smileswgt.h"
#include "smileswgt.h"
#include "abstractchatcore.h"
#include "animatedsmile.h"

QList<Smile> ChatTextWgt::m_smiles;

ChatTextWgt::ChatTextWgt(QWidget *parent)
 : QWidget(parent),
  m_smilesFromSender(0),
  m_keepAnimations(-1)
{
  QGridLayout* grid = new QGridLayout(this);
  m_text            = new TextBrowser(this);

  m_text->setOpenExternalLinks(true);

  grid->addWidget(m_text, 0, 0);
  grid->setMargin(0);
  m_text->verticalScrollBar()->setTracking(true);

  initSmiles(QChatSettings::settings()->smilesThemePath());

  connect(m_text, SIGNAL(viewportChanged())    , this, SLOT(setAnimations()));
  connect(m_text, SIGNAL(viewportVisible(bool)), this, SLOT(playPauseAnimations(bool)));
}

ChatTextWgt::~ChatTextWgt()
{
  foreach(AnimatedSmile* sm, m_animatedSmiles)
    delete sm;

  qDebug("[ChatTextWgt::~ChatTextWgt]\n");
}
//\*****************************************************************************
void ChatTextWgt::addSmile(const QStringList & smiles, const QString & name)
{
  m_smiles.append(Smile(smiles, name));
}
//\*****************************************************************************
void ChatTextWgt::initSmiles(const QString & path)
{
  QFile file(path + "/emoticons.xml");

  QDomDocument dom_document;
  QDomElement  root;
  QDomElement  child;
  QDomElement  emoticon;
  QDomElement  name;
  QDomNodeList elements;
  QDomNodeList elements_names;

  QStringList  list;

  m_smiles.clear();

  if(!file.open(QIODevice::ReadOnly))
  {
    Globals::addError("Couldn't open " + path + "/emoticons.xml");
    return;
  }

  if(!dom_document.setContent(&file, true))
  {
    Globals::addError("Couldn't parse " + path + "/emoticons.xml");
    return;
  }

  root  = dom_document.documentElement();

  elements = root.elementsByTagName("emoticon");

  for(uint i = 0, len = elements.length(); i < len; i++)
  {
    emoticon = elements.item(i).toElement();
    elements_names = emoticon.elementsByTagName("string");
    list.clear();

    for(uint j = 0, len = elements_names.length(); j < len; j++)
    {
      child = elements_names.item(j).toElement();
      list.append(child.text());
    }

    addSmile(list, emoticon.attribute("file"));
  }
}
//\*****************************************************************************
void ChatTextWgt::addMsg(const Message* msg)
{
  QTextDocument*   doc = new QTextDocument;
  QTextCharFormat  fmt;
  QTextBlockFormat bl_fmt;
  QDateTime        date_time;
  QTextCursor      cur_old = m_text->textCursor();
  QTextCursor      cur_new;
  QBrush           brush(Qt::SolidPattern);
  int              insert_begin;

  if(msg->isHtml())
    doc->setHtml(msg->msg());
  else
    doc->setPlainText(msg->msg());

  // preparing msg header(user and time information)
  // ************************
  date_time.setTime_t(msg->receiveTime());

  if(isSystemMsg(msg->type()))
    brush.setColor(QChatSettings::settings()->sysColor());
  else
    brush.setColor(msg->color());

  fmt.setForeground(brush);

  QString msg_hdr(QChatSettings::settings()->strOption("DisplayMessagesFormat"));
  QString tm_fmt = msg_hdr.section(QString("%time%"), 1, 1);

  msg_hdr.replace(QRegExp("%time%([^<]*)%time%"), date_time.toString(tm_fmt));
  msg_hdr.replace("%user", msg->userName());
  msg_hdr.replace("%comp", msg->compName());

  if(msg->requested())
    msg_hdr.prepend(">");
  // ************************

  // setting cursor to the end of document and inserting msg header text with needed format
  cur_new = m_text->textCursor();
  cur_new.clearSelection();
  cur_new.setPosition(m_text->toPlainText().size());
  cur_new.setCharFormat(fmt);
  cur_new.insertText(msg_hdr);

  // processing message and inserting smiles
  // ************************
  int idx_end = 0;
  QString str;
  QString smile;
  QString msg_ = doc->toPlainText();

  cur_new = doc->rootFrame()->firstCursorPosition();
  smile   = nextSmile  (msg_);
  idx_end = msg_.indexOf(smile);

  for(; idx_end != -1 && !smile.isEmpty();)
  {
    str     = msg_.left(idx_end);
    msg_    = msg_.right(msg_.size() - idx_end - smile.size());

    cur_new = doc->find(smile, cur_new);
    if(!cur_new.isNull())
    {
      while(cur_new.selectedText() != smile)
      {
        cur_new.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
        if(cur_new.position() == doc->toPlainText().size())
          break;
      }

      if(cur_new.selectedText() == smile)
      {
        // FIXME workarounding qt4.3 bug
        if(cur_new.currentList())
          cur_new.insertText(" ");

        insertSmile(cur_new, smile);
      }

      if(cur_new.position() == doc->toPlainText().size())
        break;
    }
    if((smile = nextSmile(msg_)).isEmpty())
      break;

    idx_end = msg_.indexOf(smile);
  }
  // ************************

  processLinks(doc);

  // inserting processed text in the end of the chat view
  cur_new = m_text->textCursor();
  insert_begin = m_text->toPlainText().size();
  cur_new.setPosition(insert_begin);

  cur_new.insertHtml(doc->toHtml());
//   cur_new.insertText(doc->toPlainText());

  // coloring whole msg accordingly to settings
  if(!(isSystemMsg(msg->type()) && !QChatSettings::settings()->boolOption("ColorWholeSystemMessage") ||
     (msg->type() == AbstractChatCore::MESSAGE  && !QChatSettings::settings()->boolOption("ColorWholeMessage"))))
  {
    cur_new.setPosition (insert_begin);
    cur_new.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
    cur_new.mergeCharFormat(fmt);
    cur_new.clearSelection();
  }

  // restoring selection
  m_text->setTextCursor(cur_old);

  playPauseAnimations(QChatSettings::settings()->boolOption("UseAnimatedSmiles"));

  // if animation is disabled we will show only the first frame except showing nothing
  if(!QChatSettings::settings()->boolOption("UseAnimatedSmiles"))
    foreach(AnimatedSmile* sm, m_animatedSmiles)
    {
      sm->setPaused(false);
      sm->nextFrame();
      sm->setPaused(true);
    }

  cur_new.insertBlock(QTextBlockFormat());

  // TODO add autoscroll settings
//   if(scrl_max)

  m_text->verticalScrollBar()->setValue(m_text->verticalScrollBar()->maximum());
  setAnimations();

  delete doc;
}
//\*****************************************************************************
void ChatTextWgt::setMsg(const QString & msg )
{
  QTextCursor cur_new = m_text->textCursor();
  cur_new.clearSelection();

  // Obrabotka soobscheniya i vstavka smailov
  int idx_end = 0;
  QString str;
  QString smile;
  QString msg_ = msg;

  cur_new.beginEditBlock();

  smile   = nextSmile  (msg_);
  idx_end = msg_.indexOf(smile);
  for(int i = 0; i < 10 && idx_end != -1 && !smile.isEmpty(); i++)
  {
    str     = msg_.left(idx_end);
    msg_    = msg_.right(msg_.size() - idx_end - smile.size());
    cur_new.insertText(str);
    insertSmile(cur_new, smile);

    if((smile = nextSmile(msg_)).isEmpty())
      break;

    idx_end = msg_.indexOf(smile);
  }

  str = msg_;
  cur_new.insertText(str);
  cur_new.endEditBlock();
}
//\*****************************************************************************
QString ChatTextWgt::nextSmile(const QString & str, Smile** smile_)
{
  int i, j;
  int idx = -1, idx1;

  int smile = -1;
  int smile_pos = -1;

  int smile_size = 0;

  for(i = 0; i < m_smiles.size(); i++)
    for(j = 0; j < m_smiles[i].smiles.size(); j++)
    {
      idx1 = str.indexOf(m_smiles[i].smiles[j]);
      if(idx1 >= 0 && (idx1 <= idx || idx < 0))
      {
        if(m_smiles[i].smiles[j].size() >= smile_size)
        {
          smile_size = m_smiles[i].smiles[j].size();
          smile      = i;
          smile_pos  = j;
          idx        = idx1;
        }
      }
    }

  if(smile >= 0 && smile_pos >= 0)
  {
    if(smile_)
      *smile_ = &m_smiles[smile];
    return m_smiles[smile].smiles[smile_pos];
  }
  else
    return QString("");


}
//\*****************************************************************************
QString ChatTextWgt::nextSmile(const QString & str) const
{
  int i, j;
  int idx = -1, idx1;

  int smile = -1;
  int smile_pos = -1;

  int smile_size = 0;

  for(i = 0; i < m_smiles.size(); i++)
    for(j = 0; j < m_smiles[i].smiles.size(); j++)
    {
      idx1 = str.indexOf(m_smiles[i].smiles[j]);
      if(idx1 >= 0 && (idx1 < idx || idx < 0 || (idx1 == idx && m_smiles[i].smiles[j].size() >= smile_size)))
      {
        smile_size = m_smiles[i].smiles[j].size();
        smile      = i;
        smile_pos  = j;
        idx        = idx1;
      }
    }

  if(m_smilesFromSender)
  {
    for(i = 0; i < m_smilesFromSender->size(); i++)
    {
      idx1 = str.indexOf((*m_smilesFromSender)[i].smiles[0]);
      if(idx1 >= 0 && (idx1 <= idx || idx < 0))
      {
        if((*m_smilesFromSender)[i].smiles[0].size() >= smile_size)
        {
          smile_size = (*m_smilesFromSender)[i].smiles[0].size();
          smile      = i;
          smile_pos  = -1;
          idx        = idx1;
        }
      }
    }
  }

  if(smile >= 0 && smile_pos >= 0)
    return m_smiles[smile].smiles[smile_pos];
  else if(smile >= 0 && smile_pos == -1)
    return (*m_smilesFromSender)[smile].smiles[0];

  return QString("");
}
//\*****************************************************************************

void ChatTextWgt::insertSmile(QTextCursor cursor, const QString & smile)
{
  switch(QChatSettings::settings()->smilesPolicy())
  {
    case QChatSettings::NoSmiles :
      return;

    case QChatSettings::DontUseSmilesFromSender :
      insertSmileFromLocalTheme(cursor, smile);
      break;

    case QChatSettings::UseSmilesFromSender :
      if(!insertSmileFromLocalTheme(cursor, smile))
         insertSmileFromSender(cursor, smile);
      break;

    case QChatSettings::AlwaysUseSmilesFromSender :
      if(!insertSmileFromSender(cursor, smile))
         insertSmileFromLocalTheme(cursor, smile);
      break;
  }
}
//\*****************************************************************************
void ChatTextWgt::setSmilesFromSender(QList<Smile> * smiles)
{
  m_smilesFromSender = smiles;
}

void ChatTextWgt::processLinks(QTextDocument* doc)
{
  QTextCursor cursor = doc->rootFrame()->firstCursorPosition();

  for(;;)
  {
    cursor = doc->find(QRegExp("\\b(http|ftp|https)://[\\S]+"), cursor);

    if(!cursor.isNull())
      cursor.insertHtml((QString("<a href=%1>%2</a>").arg(cursor.selectedText()).arg(cursor.selectedText())));
    else
      break;
  }
}

void ChatTextWgt::setAnimations()
{
  int min = m_text->cursorForPosition(QPoint(0, 0)).position();
  int max = m_text->cursorForPosition(QPoint(m_text->size().width(), m_text->size().height())).position();

  foreach(AnimatedSmile* sm, m_animatedSmiles)
    sm->pauseIfHidden(min, max);
}

bool ChatTextWgt::insertSmileFromSender(QTextCursor cursor, const QString & smile)
{
  int i;

  if(m_smilesFromSender)
  {
    for(i = 0; i < m_smilesFromSender->size(); i++)
      if((*m_smilesFromSender)[i].smiles[0] == smile)
      {
        if(QFile((*m_smilesFromSender)[i].name).exists())
        {
          cursor.insertText(" ");
          AnimatedSmile* asmile = new AnimatedSmile;
          asmile->init(cursor.position() + m_text->toPlainText().size() - 1, (*m_smilesFromSender)[i].name, m_text->document());

          m_animatedSmiles.append(asmile);

          return true;
        }

        m_smilesFromSender->clear();
        break;
      }
  }

  return false;
}

bool ChatTextWgt::insertSmileFromLocalTheme(QTextCursor cursor, const QString & smile)
{
  QString smiles_dir =  QChatSettings::settings()->smilesThemePath();
  int i, j;

  for(i = 0; i < m_smiles.size(); i++)
    for(j = 0; j < m_smiles[i].smiles.size(); j++)
      if(m_smiles[i].smiles[j] == smile)
      {
        if(QFile(smiles_dir + m_smiles[i].name).exists())
          cursor.insertImage(smiles_dir + m_smiles[i].name);
        else if(QFile(smiles_dir + m_smiles[i].name + ".png").exists())
          cursor.insertImage(smiles_dir + m_smiles[i].name + ".png");
        else if(QFile(smiles_dir + m_smiles[i].name + ".jpg").exists())
          cursor.insertImage(smiles_dir + m_smiles[i].name + ".jpg");
        else if(QFile(smiles_dir + m_smiles[i].name + ".gif").exists())
        {
          cursor.insertText(" ");
          AnimatedSmile* asmile = new AnimatedSmile;
          asmile->init(cursor.position() + m_text->toPlainText().size() - 1, (smiles_dir + m_smiles[i].name + ".gif"), m_text->document());

          m_animatedSmiles.append(asmile);
        }
        else if(QFile(smiles_dir + m_smiles[i].name + ".mng").exists())
        {
          cursor.insertText(" ");
          AnimatedSmile* asmile = new AnimatedSmile;
          asmile->init(cursor.position() + m_text->toPlainText().size() - 1, (smiles_dir + m_smiles[i].name + ".mng"), m_text->document());

          m_animatedSmiles.append(asmile);
        }

        else
          cursor.insertText(m_smiles[i].smiles[j]);

        return true;
      }

  return false;
}

void ChatTextWgt::playPauseAnimations(bool play)
{
  if(play && !QChatSettings::settings()->boolOption("UseAnimatedSmiles"))
    return;

  if(play && m_keepAnimations < 0)
    foreach(AnimatedSmile* sm, m_animatedSmiles)
      sm->start();
  else if(!play)
    foreach(AnimatedSmile* sm, m_animatedSmiles)
      sm->stop();
  else
  {
    int i, j;
    for(i = m_animatedSmiles.size() - 1, j = 0; i >= 0 && j < m_keepAnimations; i--)
    {
      if(m_animatedSmiles[i]->animated())
      {
        m_animatedSmiles[i]->start();
        j++;
      }
    }

    for(; i >= 0; i--)
      if(m_animatedSmiles[i]->animated())
        m_animatedSmiles[i]->setPaused(true);
  }

  if(play)
    setAnimations();
}
