
#include "vdricqsetup.h"
#include "icqhook.h"
#include "Settings.h"
#include "sstream_fix.h"
#include "vdricqosd.h"
#include "Dir.h"
#include "vdr/interface.h"
#include "NumberList.h"

#include "WndTitlebar.h"
#include "WndMessagelist.h"
#include "WndContact.h"
#include "WndStatusbar.h"


using std::string;
using std::ostringstream;
using std::istringstream;
using std::fstream;
using std::ofstream;
using std::cout;
using std::cerr;
using std::endl;
using std::map;
using std::runtime_error;


using SigC::slot;

using ICQ2000::ContactRef;

SimpleClient::SimpleClient(unsigned int ourUIN, const string pass)
{
  SetConnectParameters(ourUIN, pass);

  cli.connected.connect(slot(this, &SimpleClient::connected_cb));    
  cli.disconnected.connect(slot(this, &SimpleClient::disconnected_cb));
  cli.logger.connect(slot(this, &SimpleClient::logger_cb));
  cli.contactlist.connect(slot(this,&SimpleClient::contactlist_cb));
  cli.messaged.connect(slot(this,&SimpleClient::message_cb));
  cli.messageack.connect(slot(this, &SimpleClient::messageack_cb));
  cli.contact_status_change_signal.connect(slot(this,&SimpleClient::status_change_cb));
  cli.self_contact_userinfo_change_signal.connect(slot(this,&SimpleClient::self_userinfo_change_cb));
  cli.self_contact_status_change_signal.connect(slot(this,&SimpleClient::self_status_change_cb));
  cli.socket.connect(slot(this, &SimpleClient::socket_cb));
/*    cli.want_auto_resp.connect(slot(this,&SimpleClient::want_auto_resp_cb));*/
  cli.server_based_contact_list.connect(slot(this, &SimpleClient::server_based_contact_list_cb));
  cli.search_result.connect(slot(this, &SimpleClient::search_result_cb));

  m_pTitlebarWnd    = NULL;
  m_pMessagelistWnd = NULL;
  m_pContactWnd     = NULL;
  m_pStatusbarWnd   = NULL;
  
  acked         = false;
  m_bRunning    = false;
  m_bShutdown   = false;
  m_bStartUpVDR = true;
  m_iDetailInfoUpdateCounter = 0;
  m_pIgnoreList       = NULL;
  m_pWaitList         = NULL;
  m_pLostEventHistory = NULL;
}

SimpleClient::~SimpleClient(void) 
{
  delete m_pLostEventHistory;
  delete m_pIgnoreList;
  delete m_pWaitList;

  // save self-contact
  saveSelfContact();

  // save contact-specific settings
  for( map<unsigned int, string>::iterator itr = m_settingsmap.begin(); itr != m_settingsmap.end(); ++itr ) {
    ContactRef c = cli.getContact( itr->first );
    if (c.get() != NULL) saveContact( c, itr->second, false );
  }

  m_bRunning = false;

  while (!m_bShutdown)
  {
    usleep(100000);
  }

  for( map<unsigned int, History *>::iterator itr = m_histmap.begin(); itr != m_histmap.end(); ++itr )
    delete itr->second;
}


void SimpleClient::SetConnectParameters(unsigned int ourUIN, const string pass)
{
  if ((ourUIN != cli.getUIN()) || (pass != cli.getPassword()))
  {
    cli.setUIN(ourUIN);
    cli.setPassword(pass);
    ICQ2000::Status oldStatus;
    if ((oldStatus = cli.getStatus()) != ICQ2000::STATUS_OFFLINE)
    {
      cli.setStatus(ICQ2000::STATUS_OFFLINE);
      cli.setStatus(oldStatus);
    }

    m_ActiveContact   = cli.getSelfContact();
    
    m_basedir = cPlugin::ConfigDirectory("vdricq") + (string)"/";
    mkdir_BASE_DIR();
    m_basedir = m_basedir + (string)m_ActiveContact->getStringUIN() + (string)"/";
    mkdir_BASE_DIR();
    m_contactdir = m_basedir + "contacts/";
  }
}

void SimpleClient::SetTitlebarWnd(cTitlebarWnd *pTitlebarWnd)
{
  m_pTitlebarWnd = pTitlebarWnd;
  
  if (m_pTitlebarWnd != NULL)
  {
    m_pTitlebarWnd->SetStatus(cli.getStatus());
  }
}

void SimpleClient::SetMessagelistWnd(cMessagelistWnd *pMessagelistWnd)
{
  m_pMessagelistWnd = pMessagelistWnd;
  if (m_pMessagelistWnd)
    m_pMessagelistWnd->SetMessageList(&m_message_queue);
}

void SimpleClient::SetContactWnd(cContactWnd *pContactWnd)
{
  m_pContactWnd = pContactWnd;
 
 if (m_pContactWnd != NULL)
   m_pContactWnd->SetContactList(&cli.getContactList());
}

void SimpleClient::SetStatusbarWnd(cStatusbarWnd *pStatusbarWnd)
{
  m_pStatusbarWnd = pStatusbarWnd;
}


void SimpleClient::connected_cb(ICQ2000::ConnectedEvent *c) 
{
  dsyslog("vdricq: Connected");

  delete m_pIgnoreList;
  m_pIgnoreList = new cNumberList(m_contactdir + "ignorelist");
  if (!m_pIgnoreList->loadList())
    m_pIgnoreList->saveList(); // create Ignorelist File

  delete m_pWaitList;
  m_pWaitList = new cNumberList(m_contactdir + "waitlist");
  if (!m_pWaitList->loadList())
    m_pWaitList->saveList();  // create Waitlist File

  delete m_pLostEventHistory;
  m_pLostEventHistory = new History("LostEvents.history", m_contactdir);

  loadSelfContact();

  // setup contact list
  loadContactList();
}

void SimpleClient::disconnected_cb(ICQ2000::DisconnectedEvent *c) 
{
  delete m_pLostEventHistory;
  m_pLostEventHistory = NULL;
  delete m_pIgnoreList;
  m_pIgnoreList = NULL;
  delete m_pWaitList;
  m_pWaitList = NULL;

  if (c->getReason() == ICQ2000::DisconnectedEvent::REQUESTED) 
  {
    dsyslog("vdricq: Disconnected as requested");
  } else 
  {
    string sMessage;
    sMessage = (string)"vdricq: " + (string)tr("Problem connecting: ");
    switch(c->getReason()) {
    case ICQ2000::DisconnectedEvent::FAILED_LOWLEVEL:
      sMessage += tr("Socket problems");
      break;
    case ICQ2000::DisconnectedEvent::FAILED_BADUSERNAME:
      sMessage += tr("Bad Username");
      break;
    case ICQ2000::DisconnectedEvent::FAILED_TURBOING:
      sMessage += tr("Turboing");
      break;
    case ICQ2000::DisconnectedEvent::FAILED_BADPASSWORD:
      sMessage += tr("Bad Password");
      break;
    case ICQ2000::DisconnectedEvent::FAILED_MISMATCH_PASSWD:
      sMessage += tr("Username and Password did not match");
      break;
    case ICQ2000::DisconnectedEvent::FAILED_UNKNOWN:
      sMessage += tr("Unknown Error");
      break;
    case ICQ2000::DisconnectedEvent::FAILED_DUALLOGIN:
      sMessage += tr("Dual login, disconnected");
      break;
    default:
      break;
    }

    Message(mtError, sMessage.c_str());
  }

  /*
  // do another switch here since we want to return early for some of these reasons
  switch(c->getReason()) 
  {
    case ICQ2000::DisconnectedEvent::REQUESTED:
      return;
    case ICQ2000::DisconnectedEvent::FAILED_BADUSERNAME:
    case ICQ2000::DisconnectedEvent::FAILED_BADPASSWORD:
    case ICQ2000::DisconnectedEvent::FAILED_MISMATCH_PASSWD:
      gui.invalid_login_prompt();
      return;
    case ICQ2000::DisconnectedEvent::FAILED_TURBOING:
      gui.turboing_prompt();
      return;
    case ICQ2000::DisconnectedEvent::FAILED_DUALLOGIN:
      gui.duallogin_prompt();
      return;
    default:
      break;
    }

  if (m_iRetries > 0) 
  {
    --m_iRetries;
    Gtk::Main::idle.connect( bind( slot( this, &SimpleClient::idle_connect_cb ), cli.getStatusWanted(), cli.getInvisibleWanted() ) );
  } else 
  {
    m_iRetries = VdrIcqSetup.m_iConnectRetries; // reset m_retries

    if( c->getReason() == ICQ2000::DisconnectedEvent::FAILED_LOWLEVEL )
      gui.disconnect_lowlevel_prompt(m_retries);
    else if( c->getReason() == ICQ2000::DisconnectedEvent::FAILED_UNKNOWN )
      gui.disconnect_unknown_prompt(m_retries);
  }
*/
}
/*
gint SimpleClient::idle_connect_cb(ICQ2000::Status s, bool inv) 
{
  cli.setStatus( s, inv );
  return 0;
}*/

void SimpleClient::logger_cb(ICQ2000::LogEvent *c) 
{
  bool log_to_logfile = 0;

  if (log_to_logfile) 
  {
    time_t t = time(NULL);
    struct tm *tm = localtime(&t);
    char time_str[256];
    strftime(time_str, 255, "[%H:%M:%S] ", tm);

    ostringstream ostr;
    ostr << time_str;
    switch(c->getType()) 
    {
    case ICQ2000::LogEvent::INFO:
      isyslog(c->getMessage().c_str());
      break;
    case ICQ2000::LogEvent::ERROR:
      esyslog(c->getMessage().c_str());
      break;
    case ICQ2000::LogEvent::WARN:
      isyslog(c->getMessage().c_str());
      break;
    case ICQ2000::LogEvent::PACKET:
      dsyslog(c->getMessage().c_str());
      break;
    case ICQ2000::LogEvent::DIRECTPACKET:
      dsyslog(c->getMessage().c_str());
      break;
    }
  }
}


string SimpleClient::get_unique_historyname() throw (runtime_error)
{
  char tmpl[255];
  int fd;
  
  snprintf( tmpl, sizeof(tmpl), "%smobilehistory.XXXXXX", m_contactdir.c_str() );
  if( (fd = mkstemp( tmpl ) ) == -1 )
    throw( runtime_error("vdricq: Could not generate temporary filename for history") );
  close(fd);
  return string ( tmpl + m_contactdir.size() );
}
/*
void SimpleClient::socket_select_cb(int source, GdkInputCondition cond) 
{
  ostringstream ostr;

  cli.socket_cb(source, (ICQ2000::SocketEvent::Mode)cond);
}

int SimpleClient::poll_server_cb() 
{
  cli.Poll();
  return true;
}
*/
void SimpleClient::messageack_cb(ICQ2000::MessageEvent *ev) 
{
  if (ev->isFinished() && ev->isDelivered()
      && ev->getType() != ICQ2000::MessageEvent::AwayMessage) 
  {

    unsigned int uin = ev->getContact()->getUIN();
    if (m_histmap.count(uin) == 0) 
    {
      ostringstream ostr;
      ostr << "No history object for contact: " << uin;
      esyslog(ostr.str().c_str());
    } else 
    {
      History *h = m_histmap[ev->getContact()->getUIN()];
      try 
      {
        h->log(ev, false);
      } catch(runtime_error& e) 
      {
        // thrown when the history file can't be opened
        esyslog("vdricq: Can't open historyfile '%s'", e.what());
      }
    }

  }
}


void SimpleClient::socket_cb(ICQ2000::SocketEvent *ev) 
{

  if (dynamic_cast<ICQ2000::AddSocketHandleEvent*>(ev) != NULL) 
  {
    // the library requests we start selecting on a socket

    ICQ2000::AddSocketHandleEvent *cev = dynamic_cast<ICQ2000::AddSocketHandleEvent*>(ev);
    int fd = cev->getSocketHandle();

    // register this socket with our Select object
    m_sockets[fd] =
    input.connect( slot(this,&SimpleClient::select_socket_cb),
       // the slot that the Select object will callback
       fd,
       // the socket file descriptor to add
       (Select::SocketInputCondition) 
       ((cev->isRead() ? Select::Read : 0) |
        (cev->isWrite() ? Select::Write : 0) |
        (cev->isException() ? Select::Exception : 0))
       // the mode to select on it on
       );

  } else if (dynamic_cast<ICQ2000::RemoveSocketHandleEvent*>(ev) != NULL) 
  {
    // the library requests we stop selecting on a socket

    ICQ2000::RemoveSocketHandleEvent *cev = dynamic_cast<ICQ2000::RemoveSocketHandleEvent*>(ev);
    int fd = cev->getSocketHandle();

    if (m_sockets.count(fd) == 0) {
      esyslog("vdricq: file descriptor not connected");
    } else {
      m_sockets[fd].disconnect();
      m_sockets.erase(fd);
    }
  }
  
}


void SimpleClient::select_socket_cb(int fd, Select::SocketInputCondition cond)
{
  // inform the library (it is always only library sockets registered with
  // the Select object)
  cli.socket_cb(fd, (ICQ2000::SocketEvent::Mode)cond
          // dirty-hack, since they are binary compatible :-)
          );
}


void SimpleClient::message_cb(ICQ2000::MessageEvent *ev) 
{
  ContactRef c = ev->getContact();

  // this behaviour is a client choice - might be nice to make it
  // customisable eventually, for the moment we don't accept in
  // occupied or denied unless it is sent as to contact list or
  // urgent.  ignore lists will be implemented here eventually

  if ((m_pIgnoreList) && (m_pIgnoreList->inList(c->getUIN())))
    return;  // user in ignorelist

  ICQ2000::Status s = cli.getStatus();

  ICQ2000::ICQMessageEvent *icq = dynamic_cast<ICQ2000::ICQMessageEvent*>(ev);
  if (icq != NULL) {
    if ((s == ICQ2000::STATUS_OCCUPIED || s == ICQ2000::STATUS_DND)
      && !(icq->isUrgent() || icq->isToContactList())) 
    {
      ev->setDelivered(false);
      /*if (s == ICQ2000::STATUS_OCCUPIED)
        ev->setDeliveryFailureReason( ICQ2000::MessageEvent::Failed_Occupied );
      else
        ev->setDeliveryFailureReason( ICQ2000::MessageEvent::Failed_DND );
      */
      // not logged, not signalled, just ignored :-)
      return;
    }
  }
  // add to contact list if not on list already
  if (!cli.getContactList().exists(c->getUIN())) 
  {
    if ((m_pIgnoreList) && (m_pIgnoreList->inList(c->getUIN())))
      return;
    char msg[100];
    char msg2[100];
    if (icq->getType() == ICQ2000::MessageEvent::AuthReq)
    {
      ICQ2000::AuthReqEvent *pAuthMessage = dynamic_cast<ICQ2000::AuthReqEvent*>(ev);
      if ((m_pMessagelistWnd != NULL) && (pAuthMessage != NULL))
      {
        char msg2[100 + pAuthMessage->getMessage().size()];
        sprintf(msg2, tr("User %s  has requested your authorisation to add you to his/her Contact list.\n\n%s"), 
          c->getAlias().c_str(), pAuthMessage->getMessage().c_str());
        m_pMessagelistWnd->ShowMessage(msg2);
      }

      sprintf(msg2, tr("ICQ: User %s has requested your authorisation."), c->getAlias().c_str());
      EConfirmMessage eConfirmRetVal = Confirm(tr("Press OK to authorize the User."), msg2, 20);
      if (eConfirmRetVal == cmConfirmed)
      {
        ICQ2000::AuthAckEvent *pAuthEvent = new ICQ2000::AuthAckEvent(c, true);
        cli.SendEvent(pAuthEvent);
        cli.addContact(c);
      }
      else if (eConfirmRetVal == cmNotConfirmed)
      {
        if (Confirm(tr("Press OK to ignore the User.")) == cmConfirmed)
        {
          ICQ2000::AuthAckEvent *pAuthEvent = new ICQ2000::AuthAckEvent(c, false);
          cli.SendEvent(pAuthEvent);
          m_pIgnoreList->insert(m_pIgnoreList->end(), c);
          m_pIgnoreList->saveList();
        }
        else
        {
          AddToLostEventHistory(ev);
        }
      }
      else
      {
        AddToLostEventHistory(ev);
      }

      if (m_pMessagelistWnd != NULL)
        m_pMessagelistWnd->HiddeForegroundMessage();
      return;
    }
    else if (icq->getType() == ICQ2000::MessageEvent::AuthAck)
    {
      ICQ2000::AuthAckEvent *pAuthMessage = dynamic_cast<ICQ2000::AuthAckEvent*>(ev);
      
      if (pAuthMessage->isGranted())
      {
        cli.addContact(c);
        Message(mtInfo, tr("The user has accepted your request to add him/her to your Contact List."));
      }
      else
      {
        Message(mtError, tr("The user has declined your request to add him/her to your Contact List."));
      }
      m_pWaitList->removeContact(c->getUIN());
      m_pWaitList->saveList();
      if (m_pContactWnd != NULL)
        m_pContactWnd->SetContactList(&cli.getContactList());
      return;
    }
    

    if (m_pWaitList->inList(c->getUIN()))
    {
      cli.addContact(c);
      m_pWaitList->removeContact(c->getUIN());
      m_pWaitList->saveList();
    }
    else if (icq->getType() == ICQ2000::MessageEvent::UserAdd)
    {
      sprintf(msg, tr("The User %s added you to his Contactlist. Add him to List."), c->getAlias().c_str());
      sprintf(msg2, tr("ICQ: The User %s has added you to his Contactlist."), c->getAlias().c_str());
      EConfirmMessage eConfirmRetVal = Confirm(msg, msg2);
      if (eConfirmRetVal == cmConfirmed)
      {
        cli.addContact(c);
      }
      else if (eConfirmRetVal == cmNotConfirmed)
      {
        sprintf(msg, tr("Add user %s to ignorelist?."), c->getAlias().c_str());
        if (Confirm(msg) == cmConfirmed)
        {
          m_pIgnoreList->insert(m_pIgnoreList->end(), c);
          m_pIgnoreList->saveList();
        }
        else
        {
          AddToLostEventHistory(ev);
        }

        if (m_pContactWnd != NULL)
          m_pContactWnd->SetContactList(&cli.getContactList());
      }
      else if (eConfirmRetVal == cmMessage)
      {
        AddToLostEventHistory(ev);
      }
      return;
    }
    else
    {
      sprintf(msg, tr("New user %s contact you. Press OK to add him to List."), c->getAlias().c_str());
      sprintf(msg2, tr("ICQ: New user %s contact you."), c->getAlias().c_str());
      EConfirmMessage eConfirmRetVal = Confirm(msg, msg2);
      if (eConfirmRetVal == cmNotConfirmed)
      {
        sprintf(msg, tr("Add user %s to ignorelist?."), c->getAlias().c_str());
        if (Confirm(msg) == cmConfirmed)
        {
          m_pIgnoreList->insert(m_pIgnoreList->end(), c);
          m_pIgnoreList->saveList();
        }
        else
        {
          AddToLostEventHistory(ev);
        }

        if (m_pContactWnd != NULL)
          m_pContactWnd->SetContactList(&cli.getContactList());
        return;
      }
      else if (eConfirmRetVal == cmMessage)
      {
        AddToLostEventHistory(ev);
        return;
      }

      cli.addContact(c);
    }
  }
    
  ICQ2000::Contact* pContact = cli.getContact(c->getUIN()).get();
  if ((pContact == NULL) || (m_pIgnoreList->inList(c->getUIN())))
    return;  // user in ignorelist
  
  ev->setDelivered(true);

  // convert to a client event type and add to queue
  MessageEvent *ickle_ev = convert_libicq2000_event(ev);
  if (ickle_ev == NULL) 
  {
    //SignalLog(ICQ2000::LogEvent::INFO, "Unhandled libicq2000 event");
    return;
  }
  
  m_message_queue.add_to_queue(ickle_ev);

  unsigned int uin = c->getUIN();
  if (m_histmap.count(uin) == 0) 
  {
    esyslog("No history object for contact: %d", uin);
  } else 
  {
    History *h = m_histmap[c->getUIN()];
    try 
    {
      h->log(ev, true);
    } catch(runtime_error& e) 
    {
      // thrown when the history file can't be opened
      esyslog("vdricq: Can't open history file '%s'", e.what());
    }
  }

  if (m_pMessagelistWnd != NULL)
  {   
    SetActiveContact(c);
    m_pMessagelistWnd->Draw();
  }
}


void SimpleClient::AddToLostEventHistory(ICQ2000::MessageEvent *ev)
{
  try
  {
    m_pLostEventHistory->log(ev, true);
  }
  catch(runtime_error& e) 
  {
    // thrown when the history file can't be opened
    esyslog("vdricq: Can't open LostEvent history file '%s'", e.what());
  }
}


MessageEvent* SimpleClient::convert_libicq2000_event(ICQ2000::MessageEvent *ev)
{
  ICQMessageEvent *ret = NULL;
  ContactRef c = ev->getContact();
  
  switch(ev->getType()) 
  {
  case ICQ2000::MessageEvent::Normal:
    {
      ICQ2000::NormalMessageEvent *cev = static_cast<ICQ2000::NormalMessageEvent*>(ev);
      NormalICQMessageEvent *ret2 = new NormalICQMessageEvent( ev->getTime(), c, cev->getMessage(),
                   cev->getForeground(), cev->getBackground() );
      ret2->setMultiParty( cev->isMultiParty() );
      ret = ret2;
      break;
    }
  case ICQ2000::MessageEvent::URL:
    {
      ICQ2000::URLMessageEvent *cev = static_cast<ICQ2000::URLMessageEvent*>(ev);
      ret = new URLICQMessageEvent( ev->getTime(), c, cev->getMessage(),
          cev->getURL() );
      break;
    }
  case ICQ2000::MessageEvent::SMS:
    {
      ICQ2000::SMSMessageEvent *cev = static_cast<ICQ2000::SMSMessageEvent*>(ev);
      ret = new SMSICQMessageEvent( ev->getTime(), c, cev->getMessage() );
      break;
    }
  case ICQ2000::MessageEvent::SMS_Receipt:
    {
      ICQ2000::SMSReceiptEvent *cev = static_cast<ICQ2000::SMSReceiptEvent*>(ev);
      ret = new SMSReceiptICQMessageEvent( ev->getTime(), c, cev->getMessage(),
           cev->getMessageId(), cev->getSubmissionTime(),
           cev->getDeliveryTime(), cev->delivered());
      break;
    }
  case ICQ2000::MessageEvent::AuthReq:
    {
      ICQ2000::AuthReqEvent *cev = static_cast<ICQ2000::AuthReqEvent*>(ev);
      ret = new AuthReqICQMessageEvent( ev->getTime(), c, cev->getMessage() );
      break;
    }
  case ICQ2000::MessageEvent::AuthAck:
    {
      ICQ2000::AuthAckEvent *cev = static_cast<ICQ2000::AuthAckEvent*>(ev);
      ret = new AuthAckICQMessageEvent( ev->getTime(), c, cev->getMessage(), cev->isGranted() );
      break;
    }
  case ICQ2000::MessageEvent::EmailEx:
    {
      ICQ2000::EmailExEvent *cev = static_cast<ICQ2000::EmailExEvent*>(ev);
      ret = new EmailExICQMessageEvent( ev->getTime(), c, cev->getMessage() );
      break;
    }
  case ICQ2000::MessageEvent::WebPager:
    {
      ICQ2000::WebPagerEvent *cev = static_cast<ICQ2000::WebPagerEvent*>(ev);
      ret = new WebPagerICQMessageEvent( ev->getTime(), c, cev->getMessage() );
      break;
    }
  case ICQ2000::MessageEvent::UserAdd:
    {
      ret = new UserAddICQMessageEvent( ev->getTime(), c );
      break;
    }
  default:
    ret = NULL;
  }

  ICQ2000::ICQMessageEvent *icq = dynamic_cast<ICQ2000::ICQMessageEvent*>(ev);
  if (ret != NULL && icq != NULL) 
  {
    ret->setUrgent( icq->isUrgent() );
    ret->setToContactList( icq->isToContactList() );
    ret->setDirect( icq->isDirect() );
    ret->setOffline( icq->isOfflineMessage() );
  }

  return ret;
}

bool SimpleClient::mkdir_BASE_DIR()
{
  if ( mkdir( m_basedir.c_str(), 0700 ) == -1 && errno != EEXIST ) 
  {
    ostringstream ostr;
    ostr << "mkdir " << m_basedir << " failed: " << strerror(errno);
    esyslog(ostr.str().c_str());
    return false;
  }
  return true;
}


void SimpleClient::contactlist_cb(ICQ2000::ContactListEvent *ev) 
{
  if (m_pContactWnd != NULL)
    m_pContactWnd->SetContactList(&cli.getContactList());

  ContactRef c = ev->getContact();

  if (ev->getType() == ICQ2000::ContactListEvent::UserAdded) 
  {
    if ((m_settingsmap.count(c->getUIN()) > 0) || (cli.getSelfContact()->getUIN() == c->getUIN())) 
      return;

    c->userinfo_change_signal.connect(slot(this,&SimpleClient::userinfo_change_cb));
    cli.fetchDetailContactInfo(c);

    ostringstream ostr;
    ostr << m_contactdir << c->getUIN() << ".user";

    int n = 0;
    struct stat fs;
    string filename;
    filename = ostr.str();

    // ensure uniqueness
    while ( stat( filename.c_str(), &fs ) == 0 ) 
    {
      ostringstream ostr;
      n++;
      ostr << m_contactdir << c->getUIN() << "-" << n << ".user";
      filename = ostr.str();
    }
    m_settingsmap[c->getUIN()] = filename;
    if( c->isICQContact() ) 
    {
      ostringstream os;
      os << c->getUIN() << ".history";
      m_histmap[c->getUIN()] = new History( os.str(), m_contactdir );
    }
    else
      m_histmap[c->getUIN()] = new History( get_unique_historyname(), m_contactdir );


    if ( mkdir( m_contactdir.c_str(), 0700 ) == -1 && errno != EEXIST ) 
    {
      ostringstream ostr;
      ostr << "mkdir " << m_contactdir << " failed: " << strerror(errno);
      esyslog(ostr.str().c_str());
      return;
    }
    
    saveContact( c, m_settingsmap[c->getUIN()], false );

    if (m_pIgnoreList) 
    {
      m_pIgnoreList->removeContact(c->getUIN());
      m_pIgnoreList->saveList();
    }

    if (m_pWaitList) 
    {
      m_pWaitList->removeContact(c->getUIN());
      m_pWaitList->saveList();
    }
    
  }
  else if (ev->getType() == ICQ2000::ContactListEvent::UserRemoved) 
  {
    // delete .user file for this contact
    unlink( m_settingsmap[c->getUIN()].c_str() );

    // remove history file for mobile users as well, we will not be able to correctly
    // reuse this history file if the same user is added anyway
    if( !c->isICQContact() )
      unlink( string(m_contactdir + m_histmap[c->getUIN()]->getFilename()).c_str() );

    m_histmap.erase(c->getUIN());
    m_settingsmap.erase(c->getUIN());
  }
  else
  {
    dsyslog("vdricq: contactlist_cb UIN: %d, EventType: %d", c->getUIN(), ev->getType());
  }
}


void SimpleClient::status_change_cb(ICQ2000::StatusChangeEvent *ev)
{
  if (m_pContactWnd != NULL)
    m_pContactWnd->SetContactList(&cli.getContactList());
}

void SimpleClient::userinfo_change_cb(ICQ2000::UserInfoChangeEvent *ev)
{
  if (m_pContactWnd != NULL)
    m_pContactWnd->SetContactList(&cli.getContactList());

  ContactRef c = ev->getContact();
  saveContact( c, m_settingsmap[c->getUIN()], false );
  m_iDetailInfoUpdateCounter++;
}

void SimpleClient::self_userinfo_change_cb(ICQ2000::UserInfoChangeEvent *ev)
{
  saveSelfContact();
}

void SimpleClient::self_status_change_cb(ICQ2000::StatusChangeEvent *ev)
{
  if (m_pTitlebarWnd != NULL)
  {
    m_pTitlebarWnd->SetStatus(ev->getStatus());
  }
}


void SimpleClient::server_based_contact_list_cb(ICQ2000::ServerBasedContactEvent *ev)
{
  ICQ2000::ContactList ServerContactlist = ev->getContactList();
  ICQ2000::ContactList MyContactlist = cli.getContactList();
 
  ICQ2000::ContactList::iterator curr = ServerContactlist.begin();
  while (curr != ServerContactlist.end())
  {
    if ((*curr)->getUIN() < 10)
    {
      cli.removeContact((*curr)->getUIN());
    }
    else if ((!MyContactlist.exists((*curr)->getUIN())) && (!m_pIgnoreList->inList((*curr)->getUIN()))) 
    {
      cli.addContact(*curr);
      if (m_pWaitList->inList((*curr)->getUIN()))
      {
        m_pWaitList->removeContact((*curr)->getUIN());
        m_pWaitList->saveList();
      }
    }
    ++curr;
  }
}


void SimpleClient::search_result_cb(ICQ2000::SearchResultEvent *ev)
{
  if (m_pContactWnd)
    m_pContactWnd->OnSerchResult(ev);
}



void SimpleClient::saveContact(ContactRef c, const string& s, bool self)
{
  if ((c->getUIN() == cli.getSelfContact()->getUIN()) && (!self))
    return;

  Settings user;
  user.setValue( "alias", c->getAlias() );
  if (c->isICQContact())
    user.setValue( "uin", c->getUIN() );
  user.setValue( "mobile_no", c->getMobileNo() );
  user.setValue( "firstname", c->getFirstName() );
  user.setValue( "lastname", c->getLastName() );
  user.setValue( "email", c->getEmail() );

  // Main Home Info
  ICQ2000::Contact::MainHomeInfo& mhi = c->getMainHomeInfo();
  user.setValue( "city", mhi.city );
  user.setValue( "state", mhi.state );
  user.setValue( "phone", mhi.phone );
  user.setValue( "fax", mhi.fax );
  user.setValue( "street", mhi.street );
  user.setValue( "zip", mhi.zip );
  user.setValue( "country", mhi.country );
  user.setValue( "gmt", mhi.timezone );
    
  // Homepage Info
  ICQ2000::Contact::HomepageInfo& hpi = c->getHomepageInfo();
  user.setValue( "age", hpi.age );
  user.setValue( "sex", hpi.sex );
  user.setValue( "homepage", hpi.homepage );
  user.setValue( "birth_year", hpi.birth_year );
  user.setValue( "birth_month", hpi.birth_month );
  user.setValue( "birth_day", hpi.birth_day );
  user.setValue( "lang1", hpi.lang1 );
  user.setValue( "lang2", hpi.lang2 );
  user.setValue( "lang3", hpi.lang3 );
    
  // About Info
  user.setValue( "about", c->getAboutInfo() );

  // Stats
  user.setValue( "last_signon_time", c->get_signon_time() );
  user.setValue( "last_seen_online_time", c->get_last_online_time() );
  user.setValue( "last_status_change_time", c->get_last_status_change_time() );
  user.setValue( "last_message_time", c->get_last_message_time() );
  user.setValue( "last_away_msg_check_time", c->get_last_away_msg_check_time() );

  if (!self) 
  {
    // save history mapping
    user.setValue( "history_file", m_histmap[c->getUIN()]->getFilename() );
  }
  
  try 
  {
    user.save(s);
  } catch(runtime_error& e) 
  {
    esyslog("vdricq: Can't save userfile '%s'", e.what());
  }
}


void SimpleClient::saveSelfContact()
{
  saveContact( cli.getSelfContact(), m_basedir + "self.user", true );
}


void SimpleClient::loadContactList() 
{
  ICQ2000::ContactList MyContactlist = cli.getContactList();
 
  ICQ2000::ContactList::iterator curr = MyContactlist.begin();
  while (curr!= MyContactlist.end())
  {
    MyContactlist.remove((*curr)->getUIN());
    dsyslog("vdricq: Remove from ContactList %s", (*curr)->getAlias().c_str());
    ++curr;
  }

  Dir dir;
  bool bRetval = dir.list( m_contactdir + "*.user" );
  if (!bRetval)
  {
    cli.fetchServerBasedContactList();
    return;
  }

  Dir::iterator dirit = dir.begin();
  while( dirit != dir.end() ) {
    try {
      loadContact( *dirit, false );
    } catch(runtime_error& e) {
      esyslog("vdricq: Can't load contactlist %s", e.what());
    }
    ++dirit;
  }
}

void SimpleClient::loadContact(const string& s, bool self)
{
  Settings cs;
  cs.load(s);
  // don't catch runtime_error here - let it propogate up to next level
  
  cs.defaultValueUnsignedInt("uin", 0);
  unsigned int uin = cs.getValueUnsignedInt("uin");

  ContactRef c;
  if (self) c = cli.getSelfContact();
  else
  {
    if (uin != 0)
      c = ContactRef( new ICQ2000::Contact(uin) ); // construct a real contact
    else
      c = ContactRef( new ICQ2000::Contact() );    // construct a virtual contact
  }

  // only needed for backward compatibility
  // (history_file settingsentry only exists for v >= 0.2.2)
  if (c->isICQContact()) 
  {

    /* for 'real' contacts, we name the history file fixed to something
       sensible (user might want to grep it, etc..) */
    ostringstream historyfile;
    historyfile << uin << ".history";
    cs.defaultValueString("history_file", historyfile.str() );

  } else 
  {

    /* only make up the default when it actually is defaulting
       - as the get_unique_historyname uses mkstemp so will create the file
       no matter what */
    if ( cs.getValueString("history_file").size() == 0 )
      cs.defaultValueString("history_file", get_unique_historyname());

  }

  cs.defaultValueUnsignedChar("age", 0, 0, 150);
  cs.defaultValueUnsignedChar("sex", 0, 0, 2);
  cs.defaultValueUnsignedShort("birth_year", 0, 1900, 2100);
  cs.defaultValueUnsignedChar("birth_month", 0, 0, 12);
  cs.defaultValueUnsignedChar("birth_day", 0, 0, 31);

  string alias = cs.getValueString("alias");
  c->setAlias(alias);
  c->setMobileNo(cs.getValueString("mobile_no"));
  c->setFirstName(cs.getValueString("firstname"));
  c->setLastName(cs.getValueString("lastname"));
  c->setEmail(cs.getValueString("email"));
	
  // Main Home Info
  ICQ2000::Contact::MainHomeInfo& mhi = c->getMainHomeInfo();
  mhi.city = cs.getValueString("city");
  mhi.state = cs.getValueString("state");
  mhi.phone = cs.getValueString("phone");
  mhi.fax = cs.getValueString("fax");
  mhi.street = cs.getValueString("street");
  mhi.zip = cs.getValueString("zip");
  mhi.country = cs.getValueUnsignedShort("country");
  mhi.timezone = cs.getValueUnsignedChar("gmt");
	
  // Homepage Info
  ICQ2000::Contact::HomepageInfo& hpi = c->getHomepageInfo();
  hpi.age = cs.getValueUnsignedChar("age");
  hpi.sex = cs.getValueUnsignedChar("sex");
  hpi.homepage = cs.getValueString("homepage");
  hpi.birth_year = cs.getValueUnsignedShort("birth_year");
  hpi.birth_month = cs.getValueUnsignedChar("birth_month");
  hpi.birth_day = cs.getValueUnsignedChar("birth_day");
  hpi.lang1 = cs.getValueUnsignedChar("lang1");
  hpi.lang2 = cs.getValueUnsignedChar("lang2");
  hpi.lang3 = cs.getValueUnsignedChar("lang3");

  // About Info
  c->setAboutInfo( cs.getValueString("about") );
	
  // Status
  c->set_signon_time( cs.getValueUnsignedInt("last_signon_time") );
  c->set_last_online_time( cs.getValueUnsignedInt("last_seen_online_time") );
  c->set_last_status_change_time( cs.getValueUnsignedInt("last_status_change_time") );
  c->set_last_message_time( cs.getValueUnsignedInt("last_message_time") );
  c->set_last_away_msg_check_time( cs.getValueUnsignedInt("last_away_msg_check_time") );

  if (!self) {
    m_settingsmap[c->getUIN()] = s;
    m_histmap[c->getUIN()] = new History( cs.getValueString("history_file"), m_contactdir );
    cli.addContact(c);
  }
}

void SimpleClient::loadSelfContact()
{
  try 
  {
    loadContact( m_basedir + "self.user", true );
  } 
  catch(runtime_error& e) 
  {
    cli.fetchSelfDetailContactInfo();
  }
}


void SimpleClient::SetStatus(ICQ2000::Status eStatus, bool bInfisible /*= false*/) 
{
  cli.setStatus(eStatus);
  cli.setInvisible(bInfisible);

  if (eStatus != ICQ2000::STATUS_OFFLINE)
    Start();
}


void SimpleClient::Action(void) 
{
  if (m_bRunning)
    return;

  if (m_bStartUpVDR)
  {
    m_bStartUpVDR = false;

    if (VdrIcqSetup.StatusOnVDRStartup == 6)
      return;
    
    sleep(10);
    if (VdrIcqSetup.StatusOnVDRStartup < 7)
      SetStatus((ICQ2000::Status)VdrIcqSetup.StatusOnVDRStartup);
    else if (VdrIcqSetup.StatusOnVDRStartup == 7)
      SetStatus(ICQ2000::STATUS_ONLINE, true);
  }

  m_bShutdown = false;
  m_bRunning  = true;

  int iVersuch = 1;
  while ((m_bRunning) && (iVersuch < 10))
  { 
    bool b = input.run(5000);
    if (b)
      cli.Poll();

    if (cli.getStatus() == ICQ2000::STATUS_OFFLINE)
      iVersuch ++;
    else
      iVersuch = 1;
  }

  SetStatus(ICQ2000::STATUS_OFFLINE);

  m_bRunning  = false;
  m_bShutdown = true;
}

void SimpleClient::SetActiveContact(ICQ2000::ContactRef ActiveContact) 
{ 
  if (ActiveContact->getUIN() != m_ActiveContact->getUIN())
  {
    m_ActiveContact = ActiveContact; 
    if (m_pMessagelistWnd)
      m_pMessagelistWnd->Draw();
    if (m_pContactWnd)
      m_pContactWnd->Draw();
  }
}

void SimpleClient::SendMessage(ICQ2000::ContactRef c, const std::string& msg, bool multi /*= false*/)
{
  ICQ2000::NormalMessageEvent *backev = new ICQ2000::NormalMessageEvent(c, msg, multi);
  MessageEvent *ickle_ev = convert_libicq2000_event(backev);
  ((ICQMessageEvent *)ickle_ev)->SetMyMessage();
  m_message_queue.add_to_queue(ickle_ev);
  cli.SendEvent(backev);
}


void SimpleClient::Message(eMessageType MessageType, const char *pMessage, int iSeconds /*= 5*/)
{
  if (m_pStatusbarWnd)
    m_pStatusbarWnd->Message(MessageType, pMessage, iSeconds);
  else
    Skins.Message(MessageType, pMessage, iSeconds);
}

EConfirmMessage SimpleClient::Confirm(const char *pMessage, const char *pMessageIfICQClose /*=NULL*/, 
                                      int iSeconds /*= 10*/, bool WaitForTimeout /*= false*/)
{
  if (m_pStatusbarWnd)
  {
    if (m_pStatusbarWnd->Confirm(pMessage, iSeconds, WaitForTimeout))
      return cmConfirmed;
    else
      return cmNotConfirmed;
  }
  else
  {
    if (pMessageIfICQClose != NULL)
      Skins.Message(mtInfo, pMessageIfICQClose, iSeconds);
    return cmMessage;
  }
}

bool SimpleClient::IsLostEventHistoryEmpty(void)
{
  return ((m_pLostEventHistory == NULL) || (m_pLostEventHistory->size() == 0));
}

void SimpleClient::ReadLostMessage(void)
{
  if (m_pLostEventHistory)
  {
    History::Entry entry;
    m_pLostEventHistory->get_msg(0, entry);
    m_pLostEventHistory->remove_entry(0);
    ICQ2000::MessageEvent *pMessageEvent = History::EntryToMessageEvent(entry);
    if (pMessageEvent)
      message_cb(pMessageEvent);
    delete pMessageEvent;
  }
}

// The user has accepted your request to add him/her to your Contact List.

