/* $Id: History.c,v 1.5 2005/04/15 22:51:46 jurij Exp $
 * 
 * Copyright (C) 2001 Barnaby Gray <barnaby@beedesign.co.uk>.
 * Copyright (C) 2001 Nils Nordman <nino@nforced.com>.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "History.h"
#include "sstream_fix.h"
#include "vdricqosd.h"

#include <libicq2000/Contact.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>

#include <iostream>

using ICQ2000::NormalMessageEvent;
using ICQ2000::URLMessageEvent;
using ICQ2000::SMSMessageEvent;
using ICQ2000::SMSReceiptEvent;
using ICQ2000::EmailExEvent;
using ICQ2000::WebPagerEvent;
using ICQ2000::ContactRef;

using std::string;
using std::endl;
using std::out_of_range;
using std::runtime_error;
using std::ostringstream;
using std::istringstream;
using std::cerr;
using std::ifstream;
using std::ofstream;
using std::ostream;
using std::vector;
using std::streampos;

/**
 *  Note the historyfile should be given relative to the current CONTACT_DIR.
 */
History::History(const string &historyfile, string& contactdir) {
  m_contactdir = contactdir;
  m_filename = m_contactdir + historyfile;
  m_builtindex = false;
  m_size = 0;

  touch();
}

History::~History() {

}

void History::touch() {
  // should just create a blank history file if none exists
  ofstream( m_filename.c_str(), std::ios::out | std::ios::app );
}

void History::log(ICQ2000::MessageEvent *ev, bool received) throw(runtime_error) {
  Entry he;
  ofstream of;

  ContactRef c = ev->getContact();

  /* there are some other events that aren't logged in history
     (auth requests for example) */
  if (!(ev->getType() == ICQ2000::MessageEvent::Normal
	|| ev->getType() == ICQ2000::MessageEvent::URL
	|| ev->getType() == ICQ2000::MessageEvent::SMS
	|| ev->getType() == ICQ2000::MessageEvent::SMS_Receipt
	|| ev->getType() == ICQ2000::MessageEvent::EmailEx
	|| ev->getType() == ICQ2000::MessageEvent::WebPager
	|| ev->getType() == ICQ2000::MessageEvent::AuthReq
  || ev->getType() == ICQ2000::MessageEvent::UserAdd))
    return;

  of.open( m_filename.c_str(), std::ios::out | std::ios::app );
  if (!of.is_open())
    throw runtime_error( string("History::log: Could not open historyfile for writing: ") + m_filename );

  // add to index
  of.seekp( 0, std::ios::end );
  if( m_builtindex ) {
    if( m_size >= m_index.size() )
      m_index.resize( m_index.size() + 100 );
    m_index[ m_size++ ] = of.tellp();
  }

  // at the same time convert the stuff into a Entry, for the signal
  he.type = ev->getType();
  he.timestamp = ev->getTime();
  he.dir = received ? Entry::RECEIVED : Entry::SENT;
  he.UIN = ev->getContact()->getUIN();

  of << "Time: " << ev->getTime() << endl 
     << "Direction: " << ( received ? "Received" : "Sent" ) << endl
     << "UIN: " << ev->getContact()->getStringUIN() << endl;

  ICQ2000::ICQMessageEvent *icq = dynamic_cast<ICQ2000::ICQMessageEvent*>(ev);
  if (icq != NULL && icq->isUrgent()) {
    of << "Urgent: Yes" << endl;
  }
  
  if (ev->getType() == ICQ2000::MessageEvent::Normal) {

    ICQ2000::NormalMessageEvent *nev = static_cast<ICQ2000::NormalMessageEvent*>(ev);
    he.message = nev->getMessage();
    he.offline = nev->isOfflineMessage();
    he.multiparty = nev->isMultiParty();

    of << "Type: Normal" << endl
       << "Offline: " << ( nev->isOfflineMessage() ? "Yes" : "No" ) << endl
       << "Multiparty: " << ( nev->isMultiParty() ? "Yes" : "No" ) << endl
       << "Message: ";

    quote_output( of, nev->getMessage() );
    of << endl;
      
  } else if (ev->getType() == ICQ2000::MessageEvent::URL) {

    ICQ2000::URLMessageEvent *uev = static_cast<ICQ2000::URLMessageEvent*>(ev);
    he.message = uev->getMessage();
    he.offline = uev->isOfflineMessage();
    he.URL = uev->getURL();

    of << "Type: URL" << endl
       << "Offline: " << ( uev->isOfflineMessage() ? "Yes" : "No" ) << endl
       << "Message: ";

    quote_output( of, uev->getMessage() );
    of << "URL: ";
    quote_output( of, uev->getURL() );
    of << endl;
      
  } else if (ev->getType() == ICQ2000::MessageEvent::SMS) {

    ICQ2000::SMSMessageEvent *sev = static_cast<ICQ2000::SMSMessageEvent*>(ev);
    he.message = sev->getMessage();

    of << "Type: SMS" << endl;
    if (!received) {
      of << "Receipt: " << ( sev->getRcpt() ? "Yes" : "No" ) << endl;
      he.receipt = sev->getRcpt();
    }
    of << "Message: ";
    quote_output( of, sev->getMessage() );
    of << endl;

  } else if (ev->getType() == ICQ2000::MessageEvent::SMS_Receipt) {

    ICQ2000::SMSReceiptEvent *srev = static_cast<ICQ2000::SMSReceiptEvent*>(ev);
    he.message = srev->getMessage();

    of << "Type: SMSReceipt" << endl;
    of << "Delivered: " << ( srev->delivered() ? "Yes" : "No" ) << endl;
    of << "Message: ";
    quote_output( of, srev->getMessage() );
    of << endl;
  } else if (ev->getType() == ICQ2000::MessageEvent::EmailEx) {

    ICQ2000::EmailExEvent *ee = static_cast<ICQ2000::EmailExEvent*>(ev);
    he.message = ee->getMessage();
    he.email   = ee->getEmail();
    he.sender  = ee->getSender();

    of << "Type: EmailExpress" << endl
       << "EMail: " << ee->getEmail() << endl
       << "Sender: " << ee->getSender() << endl
       << "Message: ";
    quote_output( of, ee->getMessage() );
    of << endl;
  } else if (ev->getType() == ICQ2000::MessageEvent::WebPager) {

    ICQ2000::WebPagerEvent *ee = static_cast<ICQ2000::WebPagerEvent*>(ev);
    he.message = ee->getMessage();
    he.email   = ee->getEmail();
    he.sender  = ee->getSender();

    of << "Type: WebPager" << endl
       << "EMail: " << ee->getEmail() << endl
       << "Sender: " << ee->getSender() << endl
       << "Message: ";
    quote_output( of, ee->getMessage() );
    of << endl;
  } else if (ev->getType() == ICQ2000::MessageEvent::AuthReq) {
    ICQ2000::AuthReqEvent *ee = static_cast<ICQ2000::AuthReqEvent*>(ev);
    he.message = ee->getMessage();

    of << "Type: AuthReq" << endl
       << "Message: ";
    quote_output( of, ee->getMessage() );
    of << endl;
  } else if (ev->getType() == ICQ2000::MessageEvent::UserAdd) {
    of << "Type: UserAdd" << endl;
  }

  of.close();
  new_entry.emit( &he );
}

void History::quote_output(ostream& ostr, const string& text) {
  int a = 0, l;
  while ( (l = text.find( '\n', a )) != -1 ) {
    ostr << text.substr( a, l-a ) << "\\" << endl;
    a = l + 1;
  }
  ostr << text.substr( a ) << " " << endl;
}

void History::build_index() {
  ifstream infile;
  string s;
  m_size = 0;
  m_index.clear();
  bool escaped = false; // was this empty line escaped?

  infile.open( m_filename.c_str() );

  if( !infile.is_open() ) { // this does not warrant an exception
    cerr << "Could not open historyfile for reading: " << m_filename << endl;
    return;
  }

  bool newSection = true;
  while (true) {
    do
    {
      getline( infile, s );
    } while ((!s.size()) && (newSection) && (!infile.eof()));
    if( infile.eof() )
      break;

    if (newSection)
    {
      if( m_size >= m_index.size() )
        m_index.resize( m_index.size() + 1 );
      m_index[ m_size++ ] = infile.tellg();
      newSection = false;
    }

    if( !s.size() && !escaped ) 
      newSection = true;

    escaped = s[ s.size() -1 ] == '\\';
  }
  
  infile.close();
  m_builtindex = true;
}

void History::get_msg(unsigned int index, Entry &e) throw(out_of_range,runtime_error) 
{
  ifstream infile;
  string s,s2;

  if( !m_builtindex )
    build_index();

  if( index >= m_size ) {
    ostringstream os;
    os << "History::get_msg illegal index: " << index;
    throw out_of_range( os.str() );
  }
  
  infile.open( m_filename.c_str() );

  if( !infile.is_open() )
    throw runtime_error( string("History::get_msg: Could not open historyfile for reading: ") + m_filename );

  infile.seekg( m_index[ index ] );

  e.UIN = 0;
  e.timestamp = 0;
  e.multiparty = false;
  e.dir = Entry::SENT;
  e.offline = false;
  e.urgent = false;
  e.receipt = false;
  e.delivered = true;
  e.message.erase();
  e.URL.erase();

  while (true) {
    getline( infile, s );
    if( infile.eof() || !s.size() ) // eof or end of entry
      break;

    if( s.find( "Type: " ) != string::npos ) {
      s2 = s.substr( string( "Type: ").size() );
      if( s2 == "Normal" )
        e.type = ICQ2000::MessageEvent::Normal;
      else if( s2 == "SMS" )
        e.type = ICQ2000::MessageEvent::SMS;
      else if( s2 == "SMSReceipt" )
        e.type = ICQ2000::MessageEvent::SMS_Receipt;
      else if( s2 == "EmailExpress" )
        e.type = ICQ2000::MessageEvent::EmailEx;
      else if( s2 == "WebPager" )
        e.type = ICQ2000::MessageEvent::WebPager;
      else if( s2 == "URL" )
        e.type = ICQ2000::MessageEvent::URL;
      else if( s2 == "AuthReq" )
        e.type = ICQ2000::MessageEvent::AuthReq;
      else if( s2 == "UserAdd" )
        e.type = ICQ2000::MessageEvent::UserAdd;
    }
    else if( s.find( "Time: " ) != string::npos ) {
      istringstream iss( s.substr( string( "Time: ").size() ) );
      iss >> e.timestamp;
    }
    else if( s.find( "UIN: " ) != string::npos ) {
      istringstream istr(s.substr( string( "UIN: ").size() ));
      istr >> e.UIN;
    }
    else if( s.find( "Direction: " ) != string::npos ) {
      s2 = s.substr( string( "Direction: ").size() );
      if( s2 == "Sent" )
        e.dir = Entry::SENT;
      else if( s2 == "Received" )
        e.dir = Entry::RECEIVED;
    }
    else if( s.find( "Offline: " ) != string::npos ) {
      e.offline = s.substr( string( "Offline: ").size() ) == "Yes";
    }
    else if( s.find( "Multiparty: " ) != string::npos ) {
      e.multiparty = s.substr( string( "Multiparty: ").size() ) == "Yes";
    }
    else if( s.find( "URL: " ) != string::npos ) {
      e.URL = s.substr( string( "URL: ").size() );
    }
    else if( s.find( "Receipt: " ) != string::npos ) {
      e.receipt = s.substr( string( "Receipt: ").size() ) == "Yes";
    }
    else if( s.find( "Delivered: " ) != string::npos ) {
      e.delivered = s.substr( string( "Delivered: ").size() ) == "Yes";
    }
    else if( s.find( "Urgent: " ) != string::npos ) {
      e.urgent = s.substr( string( "Urgent: ").size() ) == "Yes";
    }
    else if( s.find( "EMail: " ) != string::npos ) {
      e.email = s.substr( string( "EMail: ").size() );
    }
    else if( s.find( "Sender: " ) != string::npos ) {
      e.sender = s.substr( string( "Sender: ").size() );
    }
    else if( s.find( "Message: " ) != string::npos ) {
      int start = string( "Message: ").size();
      e.message = s.substr( start, s.size() - start - 1 );
      while( s[ s.size() - 1 ] != ' ' ) {
        e.message += '\n';
        getline( infile, s );
        e.message += s.substr(0, s.size() - 1 );
      }
    }
  }
  infile.close();
}

unsigned int History::size()
{
  if( !m_builtindex )
    build_index();

  return m_size;
}

/**
  Returns the name of the current historyfile relative to the current m_contactdir.
*/
string History::getFilename() const
{
  return m_filename.substr( m_contactdir.size() );
}

/**
  Convert Entry to MessageEvent.
*/
ICQ2000::MessageEvent *History::EntryToMessageEvent(Entry &he)
{
  ICQ2000::MessageEvent *ret = NULL;

  if (he.UIN == 0)
    return NULL;
  ContactRef c = ContactRef( new ICQ2000::Contact(he.UIN) );

  switch(he.type) 
  {
  case ICQ2000::MessageEvent::Normal:
    {
      ret = new ICQ2000::NormalMessageEvent( c, he.message, he.timestamp, he.multiparty );
      break;
    }
  case ICQ2000::MessageEvent::URL:
    {
      ret = new ICQ2000::URLMessageEvent(  c, he.message, he.URL, he.timestamp );
      break;
    }
  case ICQ2000::MessageEvent::SMS:
    {
      ret = new ICQ2000::SMSMessageEvent( c, he.message, he.receipt );
      break;
    }
  case ICQ2000::MessageEvent::SMS_Receipt:
    {
      ret = new ICQ2000::SMSReceiptEvent( c, he.message, he.message_id, "", "", he.delivered);
      break;
    }
  case ICQ2000::MessageEvent::AuthReq:
    {
      ret = new ICQ2000::AuthReqEvent( c,  he.message, he.timestamp );
      break;
    }
  case ICQ2000::MessageEvent::EmailEx:
    {
      ret = new ICQ2000::EmailExEvent( c, he.email, he.sender, he.message );
      break;
    }
  case ICQ2000::MessageEvent::WebPager:
    {
      ret = new ICQ2000::WebPagerEvent( c, he.email, he.sender, he.message );
      break;
    }
  case ICQ2000::MessageEvent::UserAdd:
    {
      ret = new ICQ2000::UserAddEvent( c );
      break;
    }
  default:
    ret = NULL;
  }

  return ret;
}


/**
  Remove Entry from the File.
*/
void History::remove_entry(unsigned int index) throw(out_of_range,runtime_error)
{
  ifstream infile;
  ofstream outfile;

  if( !m_builtindex )
    build_index();


  if( index >= m_size ) 
  {
    ostringstream os;
    os << "History::get_msg illegal index: " << index;
    throw out_of_range( os.str() );
  }

  infile.open( m_filename.c_str());
  if( !infile.is_open() )
    throw runtime_error( string("History::get_msg: Could not open historyfile for reading: ") + m_filename );

  string newfilename(m_filename + ".new");
  outfile.open( newfilename.c_str(), std::ios::out | std::ios::trunc );
  if( !outfile.is_open() )
  {
    infile.close();
    throw runtime_error( string("History::get_msg: Could not create historyfile for writing: ") + newfilename );
  }

  string s;
  while (true) 
  {
    getline( infile, s );
    if( infile.eof() )
      break;

    if (((index > 0))         && (infile.tellg() <  m_index[ index ]) || 
        ((index < m_size - 1) && (infile.tellg() >= m_index[ index + 1 ])))
      outfile << s << endl;
  }

  outfile.close();
  infile.close();
  unlink(m_filename.c_str());
  rename(newfilename.c_str(), m_filename.c_str());
  m_builtindex = false;
}
