text: /*
   * Copyright (c) 2012 - 2014 TideSDK contributors
   * http://www.tidesdk.org
   * Refer to LICENSE for details of distribution and use.
   **/
  
  /*
      cpIRC - C++ class based IRC protocol wrapper
      Copyright (C) 2003 Iain Sheppard
  
      This library is free software; you can redistribute it and/or
      modify it under the terms of the GNU Lesser General Public
      License as published by the Free Software Foundation; either
      version 2.1 of the License, or (at your option) any later version.
  
      This library 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
      Lesser General Public License for more details.
  
      You should have received a copy of the GNU Lesser General Public
      License along with this library; if not, write to the Free Software
      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  
      Contacting the author:
      ~~~~~~~~~~~~~~~~~~~~~~
  
      email:    iainsheppard@yahoo.co.uk
      IRC:    #magpie @ irc.quakenet.org
  
      Numeorous minor changes by Jeff Haynie to get it working well under TideSDK
  */
  
  #include "IRC.h"
  #ifdef OS_WIN32
  #include <windows.h>
  #include <winsock.h>
  #else
  #include <stdlib.h>
  #include <unistd.h>
  #include <errno.h>
  #include <string.h>
  #include <netdb.h>
  #include <sys/types.h>
  #include <netinet/in.h>
  #include <sys/socket.h>
  #include <cstdio>
  #define closesocket(s) close(s)
  #define SOCKET_ERROR -1
  #define INVALID_SOCKET -1
  #endif
  
  #include <iostream>
  
  IRC::IRC()
  {
      hooks=0;
      chan_users=0;
      connected=false;
      connecting=false;
      sentnick=false;
      sentpass=false;
      sentuser=false;
      cur_nick=0;
  }
  
  IRC::~IRC()
  {
      if (hooks)
          delete_irc_command_hook(hooks);
      if (cur_nick)
          delete [] cur_nick;
      cur_nick = 0;
  }
  
  void IRC::insert_irc_command_hook(irc_command_hook* hook, char* cmd_name, int (*function_ptr)(char *, char*, irc_reply_data*, void*, void*), void *pd)
  {
      if (hook->function)
      {
          if (!hook->next)
          {
              hook->next=new irc_command_hook;
              hook->next->function=0;
              hook->next->irc_command=0;
              hook->next->next=0;
              hook->next->private_data = pd;
          }
          insert_irc_command_hook(hook->next, cmd_name, function_ptr, pd);
      }
      else
      {
          hook->function=function_ptr;
          if (cmd_name == NULL)
          {
              hook->irc_command = NULL;
          }
          else
          {
              hook->irc_command=new char[strlen(cmd_name)+1];
              strcpy(hook->irc_command, cmd_name);
          }
      }
  }
  
  void IRC::hook_irc_command(char* cmd_name, int (*function_ptr)(char*, char*, irc_reply_data*, void*, void*), void *pd)
  {
      if (!hooks)
      {
          hooks=new irc_command_hook;
          hooks->function=0;
          hooks->irc_command=0;
          hooks->next=0;
          hooks->private_data=pd;
          insert_irc_command_hook(hooks, cmd_name, function_ptr,pd);
      }
      else
      {
          insert_irc_command_hook(hooks, cmd_name, function_ptr,pd);
      }
  }
  
  void IRC::delete_irc_command_hook(irc_command_hook* cmd_hook)
  {
      if (cmd_hook->next)
          delete_irc_command_hook(cmd_hook->next);
      if (cmd_hook->irc_command)
          delete cmd_hook->irc_command;
      delete cmd_hook;
  }
  
  void IRC::send(const char *fmt, ...)
  {
      va_list args;
      va_start(args, fmt);
      char szBuf[1024];
  #ifdef OS_WIN32
      int size = vsprintf_s(szBuf, 1024, fmt, args);
  #else
      int size = vsprintf(szBuf, fmt, args);
  #endif
      if (size > 0)
      {
  #ifdef DEBUG
          PRINTD("IRC sending[" << size << "] => " << szBuf);
  #endif
          ::send(irc_socket,(const char*)szBuf,size,0);
      }
  }
  
  int IRC::start(char* server, int port, char* nick, char* user, char* name, char* pass)
  {
      #ifdef WIN32
      HOSTENT* resolv;
      #else
      hostent* resolv;
      #endif
      sockaddr_in rem;
  
      if (connected)
          return 1;
  
      irc_socket=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
      if (irc_socket==INVALID_SOCKET)
      {
          return 1;
      }
      resolv=gethostbyname(server);
      if (!resolv)
      {
          closesocket(irc_socket);
          return 1;
      }
      memcpy(&rem.sin_addr, resolv->h_addr, 4);
      rem.sin_family=AF_INET;
      rem.sin_port=htons(port);
  
      connecting = true;
  
      if (connect(irc_socket, (const sockaddr*)&rem, sizeof(rem))==SOCKET_ERROR)
      {
  #ifdef WIN32
          Logger* logger = Logger::Get("Network.IRC");
          logger->Error("Failed to connect: %i", WSAGetLastError());
  #endif
          connecting = false;
          closesocket(irc_socket);
          return 1;
      }
  
      cur_nick=new char[strlen(nick)+1];
      strcpy(cur_nick, nick);
  
      send("PASS %s\r\n", pass);
      send("NICK %s\r\n", cur_nick);
      send("USER %s * 0 :%s\r\n", user, name);
  
      connected=true;
      connecting=false;
  
      return 0;
  }
  
  void IRC::disconnect()
  {
      if (connected)
      {
          PRINTD("Disconnected from server");
          connected=false;
          quit("Leaving");
  #ifdef WIN32
          shutdown(irc_socket, 2);
  #endif
          closesocket(irc_socket);
      }
  }
  
  int IRC::quit(const char* quit_message)
  {
      if (connected)
      {
          if (quit_message)
              send("QUIT %s\r\n", quit_message);
          else
              send("QUIT\r\n");
          return 0;
      }
      return 0;
  }
  
  int IRC::message_loop()
  {
      char buffer[4096];
      int ret_len;
  
      PRINTD("ENTER IRC::message_loop()");
  
      if (!connected)
      {
          while (!connected && connecting)
          {
  #ifdef WIN32
              Sleep(1);
  #else
              sleep(1);
  #endif
          }
      }
  
      int rc = 0;
  
      while (connected)
      {
          ret_len=recv(irc_socket, buffer, 4095, 0);
          if (ret_len==SOCKET_ERROR || !ret_len)
          {
              rc = 1;
              break;
          }
          buffer[ret_len]='\0';
          try
          {
              split_to_replies(buffer);
          }
          catch(std::exception &e)
          {
              Logger* logger = Logger::Get("Network.IRC");
              logger->Error("ERROR DISPATCHING IRC RECEIVE BUFFER. Error=%s", e.what());
          }
          catch(...)
          {
              Logger* logger = Logger::Get("Network.IRC");
              logger->Error("UNKNOWN EXCEPTION IN IRC RECEIVE THREAD...");
          }
      }
  
      PRINTD("EXIT IRC::message_loop() => " << rc);
      return rc;
  }
  
  void IRC::split_to_replies(char* data)
  {
      char* p;
  
      while ((p=strstr(data, "\r\n")))
      {
          *p='\0';
          parse_irc_reply(data);
          data=p+2;
      }
  }
  
  int IRC::is_op(char* channel, char* nick)
  {
      channel_user* cup;
  
      cup=chan_users;
  
      while (cup)
      {
          if (!strcmp(cup->channel, channel) && !strcmp(cup->nick, nick))
          {
              return cup->flags&IRC_USER_OP;
          }
          cup=cup->next;
      }
  
      return 0;
  }
  
  int IRC::is_voice(char* channel, char* nick)
  {
      channel_user* cup;
  
      cup=chan_users;
  
      while (cup)
      {
          if (!strcmp(cup->channel, channel) && !strcmp(cup->nick, nick))
          {
              return cup->flags&IRC_USER_VOICE;
          }
          cup=cup->next;
      }
  
      return 0;
  }
  
  channel_user* IRC::get_users()
  {
      return chan_users;
  }
  
  void IRC::parse_irc_reply(char* data)
  {
      char* hostd;
      char* cmd;
      char* params;
      irc_reply_data hostd_tmp;
      channel_user* cup;
      char* p;
      char* chan_temp;
  
      hostd_tmp.target=0;
  
      PRINTD("IRC INCOMING: " << data);
  
      if (data[0]==':')
      {
          hostd=&data[1];
          cmd=strchr(hostd, ' ');
          if (!cmd)
              return;
          *cmd='\0';
          cmd++;
          params=strchr(cmd, ' ');
          if (params)
          {
              *params='\0';
              params++;
          }
          hostd_tmp.nick=hostd;
          hostd_tmp.ident=strchr(hostd, '!');
          if (hostd_tmp.ident)
          {
              *hostd_tmp.ident='\0';
              hostd_tmp.ident++;
              hostd_tmp.host=strchr(hostd_tmp.ident, '@');
              if (hostd_tmp.host)
              {
                  *hostd_tmp.host='\0';
                  hostd_tmp.host++;
              }
          }
  
          if (!strcmp(cmd, "JOIN"))
          {
              cup=chan_users;
              if (cup)
              {
                  while (cup->nick)
                  {
                      if (!cup->next)
                      {
                          cup->next=new channel_user;
                          cup->next->channel=0;
                          cup->next->flags=0;
                          cup->next->next=0;
                          cup->next->nick=0;
                      }
                      cup=cup->next;
                  }
                  cup->channel=new char[strlen(params)+1];
                  strcpy(cup->channel, params);
                  cup->nick=new char[strlen(hostd_tmp.nick)+1];
                  strcpy(cup->nick, hostd_tmp.nick);
              }
          }
          else if (!strcmp(cmd, "PART"))
          {
              channel_user* d;
              channel_user* prev;
  
              d=0;
              prev=0;
              cup=chan_users;
              while (cup)
              {
                  if (!strcmp(cup->channel, params) && !strcmp(cup->nick, hostd_tmp.nick))
                  {
                      d=cup;
                      break;
                  }
                  else
                  {
                      prev=cup;
                  }
                  cup=cup->next;
              }
              if (d)
              {
                  if (d==chan_users)
                  {
                      chan_users=d->next;
                      if (d->channel)
                          delete [] d->channel;
                      if (d->nick)
                          delete [] d->nick;
                      delete d;
                  }
                  else
                  {
                      if (prev)
                      {
                          prev->next=d->next;
                      }
                      chan_users=d->next;
                      if (d->channel)
                          delete [] d->channel;
                      if (d->nick)
                          delete [] d->nick;
                      delete d;
                  }
              }
          }
          else if (!strcmp(cmd, "QUIT"))
          {
              channel_user* d;
              channel_user* prev;
  
              d=0;
              prev=0;
              cup=chan_users;
              while (cup)
              {
                  if (!strcmp(cup->nick, hostd_tmp.nick))
                  {
                      d=cup;
                      if (d==chan_users)
                      {
                          chan_users=d->next;
                          if (d->channel)
                              delete [] d->channel;
                          if (d->nick)
                              delete [] d->nick;
                          delete d;
                      }
                      else
                      {
                          if (prev)
                          {
                              prev->next=d->next;
                          }
                          if (d->channel)
                              delete [] d->channel;
                          if (d->nick)
                              delete [] d->nick;
                          delete d;
                      }
                      break;
                  }
                  else
                  {
                      prev=cup;
                  }
                  cup=cup->next;
              }
          }
          else if (!strcmp(cmd, "MODE"))
          {
              char* chan;
              char* changevars;
              channel_user* cup;
              channel_user* d;
              char* tmp;
              int i;
              bool plus;
  
              chan=params;
              params=strchr(chan, ' ');
              *params='\0';
              params++;
              changevars=params;
              params=strchr(changevars, ' ');
              if (!params)
              {
                  return;
              }
              if (chan[0]!='#')
              {
                  return;
              }
              *params='\0';
              params++;
  
              plus=false;
              for (i=0; i<(signed)strlen(changevars); i++)
              {
                  switch (changevars[i])
                  {
                  case '+':
                      plus=true;
                      break;
                  case '-':
                      plus=false;
                      break;
                  case 'o':
                      tmp=strchr(params, ' ');
                      if (tmp)
                      {
                          *tmp='\0';
                          tmp++;
                      }
                      tmp=params;
                      if (plus)
                      {
                          // user has been opped (chan, params)
                          cup=chan_users;
                          d=0;
                          while (cup)
                          {
                              if (cup->next && cup->channel)
                              {
                                  if (!strcmp(cup->channel, chan) && !strcmp(cup->nick, tmp))
                                  {
                                      d=cup;
                                      break;
                                  }
                              }
                              cup=cup->next;
                          }
                          if (d)
                          {
                              d->flags=d->flags|IRC_USER_OP;
                          }
                      }
                      else
                      {
                          // user has been deopped (chan, params)
                          cup=chan_users;
                          d=0;
                          while (cup)
                          {
                              if (!strcmp(cup->channel, chan) && !strcmp(cup->nick, tmp))
                              {
                                  d=cup;
                                  break;
                              }
                              cup=cup->next;
                          }
                          if (d)
                          {
                              d->flags=d->flags^IRC_USER_OP;
                          }
                      }
                      params=tmp;
                      break;
                  case 'v':
                      tmp=strchr(params, ' ');
                      if (tmp)
                      {
                          *tmp='\0';
                          tmp++;
                      }
                      if (plus)
                      {
                          // user has been voiced
                          cup=chan_users;
                          d=0;
                          while (cup)
                          {
                              if (!strcmp(cup->channel, params) && !strcmp(cup->nick, hostd_tmp.nick))
                              {
                                  d=cup;
                                  break;
                              }
                              cup=cup->next;
                          }
                          if (d)
                          {
                              d->flags=d->flags|IRC_USER_VOICE;
                          }
                      }
                      else
                      {
                          // user has been devoiced
                          cup=chan_users;
                          d=0;
                          while (cup)
                          {
                              if (!strcmp(cup->channel, params) && !strcmp(cup->nick, hostd_tmp.nick))
                              {
                                  d=cup;
                                  break;
                              }
                              cup=cup->next;
                          }
                          if (d)
                          {
                              d->flags=d->flags^IRC_USER_VOICE;
                          }
                      }
                      params=tmp;
                      break;
                  default:
                      return;
                      break;
                  }
                  // ------------ END OF MODE ---------------
              }
          }
          else if (!strcmp(cmd, "353"))
          {
              // receiving channel names list
              if (!chan_users)
              {
                  chan_users=new channel_user;
                  chan_users->next=0;
                  chan_users->nick=0;
                  chan_users->flags=0;
                  chan_users->channel=0;
              }
              cup=chan_users;
              chan_temp=strchr(params, '#');
              if (chan_temp)
              {
                  //chan_temp+=3;
                  p=strstr(chan_temp, " :");
                  if (p)
                  {
                      *p='\0';
                      p+=2;
                      while (strchr(p, ' '))
                      {
                          char* tmp;
  
                          tmp=strchr(p, ' ');
                          *tmp='\0';
                          tmp++;
                          while (cup->nick)
                          {
                              if (!cup->next)
                              {
                                  cup->next=new channel_user;
                                  cup->next->channel=0;
                                  cup->next->flags=0;
                                  cup->next->next=0;
                                  cup->next->nick=0;
                              }
                              cup=cup->next;
                          }
                          if (p[0]=='@')
                          {
                              cup->flags=cup->flags|IRC_USER_OP;
                              p++;
                          }
                          else if (p[0]=='+')
                          {
                              cup->flags=cup->flags|IRC_USER_VOICE;
                              p++;
                          }
                          cup->nick=new char[strlen(p)+1];
                          strcpy(cup->nick, p);
                          cup->channel=new char[strlen(chan_temp)+1];
                          strcpy(cup->channel, chan_temp);
                          p=tmp;
                      }
                      while (cup->nick)
                      {
                          if (!cup->next)
                          {
                              cup->next=new channel_user;
                              cup->next->channel=0;
                              cup->next->flags=0;
                              cup->next->next=0;
                              cup->next->nick=0;
                          }
                          cup=cup->next;
                      }
                      if (p[0]=='@')
                      {
                          cup->flags=cup->flags|IRC_USER_OP;
                          p++;
                      }
                      else if (p[0]=='+')
                      {
                          cup->flags=cup->flags|IRC_USER_VOICE;
                          p++;
                      }
                      cup->nick=new char[strlen(p)+1];
                      strcpy(cup->nick, p);
                      cup->channel=new char[strlen(chan_temp)+1];
                      strcpy(cup->channel, chan_temp);
                  }
              }
          }
          else if (!strcmp(cmd, "NOTICE"))
          {
              hostd_tmp.target=params;
              params=strchr(hostd_tmp.target, ' ');
              if (params)
                  *params='\0';
              params++;
          }
          else if (!strcmp(cmd, "PRIVMSG"))
          {
              hostd_tmp.target=params;
              params=strchr(hostd_tmp.target, ' ');
              if (!params)
                  return;
              *(params++)='\0';
              std::string d(&params[1]);
              std::string h(hostd_tmp.nick);
              if (d.find("VERSION")==1 && h.find("freenode-connect")==0)
              {
                  // ignore this message
                  return;
              }
              #ifdef __IRC_DEBUG__
              PRINTD(hostd_tmp.target << ": <" << hostd_tmp.nick << "> " << &params[1]);
              #endif
          }
          else if (!strcmp(cmd, "NICK"))
          {
  //            if (!strcmp(hostd_tmp.nick, cur_nick))
              // if the nicks don't match get the new one.
              if (strcmp(hostd_tmp.nick, cur_nick))
              {
                  delete [] cur_nick;
                  cur_nick=new char[strlen(params)+1];
                  strcpy(cur_nick, params);
              }
          }
          /* else if (!strcmp(cmd, ""))
          {
              #ifdef __IRC_DEBUG__
              #endif
          } */
          call_hook(cmd, params, &hostd_tmp);
      }
      else
      {
          cmd=data;
          data=strchr(cmd, ' ');
          if (!data)
              return;
          *data='\0';
          params=data+1;
  
          if (!strcmp(cmd, "PING"))
          {
              if (!params)
                  return;
              send("PONG %s\r\n", &params[1]);
          }
          else
          {
              hostd_tmp.host=0;
              hostd_tmp.ident=0;
              hostd_tmp.nick=0;
              hostd_tmp.target=0;
              call_hook(cmd, params, &hostd_tmp);
          }
      }
  }
  
  void IRC::call_hook(char* irc_command, char* params, irc_reply_data* hostd)
  {
      irc_command_hook* p;
  
      if (!hooks)
          return;
  
      p=hooks;
      while (p)
      {
          //JGH: added wildcard ability by setting null
          if (p->irc_command == NULL || !strcmp(p->irc_command, irc_command))
          {
              //JGH: added private data pointer in callback
              (*(p->function))(irc_command, params, hostd, this, p->private_data);
              p=0;
          }
          else
          {
              p=p->next;
          }
      }
  }
  
  int IRC::notice(char* target, char* message)
  {
      if (!connected)
          return 1;
      send("NOTICE %s :%s\r\n", target, message);
      return 0;
  }
  
  int IRC::notice(char* fmt, ...)
  {
      va_list argp;
  
      if (!connected)
          return 1;
      va_start(argp, fmt);
      char szBuf[1024];
      send("NOTICE %s :", fmt);
  #ifdef OS_WIN32
      int count = vsprintf_s(szBuf, va_arg(argp,char*), argp);
  #else
      int count = vsprintf(szBuf, va_arg(argp,char*), argp);
  #endif
      if (count>0) send(szBuf);
      va_end(argp);
      send("\r\n");
      return 0;
  }
  
  int IRC::privmsg(char* target, char* message)
  {
      if (!connected)
          return 1;
      send("PRIVMSG %s :%s\r\n", target, message);
      return 0;
  }
  
  int IRC::privmsg(char* fmt, ...)
  {
      va_list argp;
  
      if (!connected)
          return 1;
      va_start(argp, fmt);
      send("PRIVMSG %s :", fmt);
      char szBuf[1024];
  #ifdef OS_WIN32
      int count = vsprintf_s(szBuf, va_arg(argp,char*), argp);
  #else
      int count = vsprintf(szBuf, va_arg(argp,char*), argp);
  #endif
      if (count>0) send(szBuf);
      va_end(argp);
      send("\r\n");
      return 0;
  }
  
  
  int IRC::join(char* channel)
  {
      if (!connected)
          return 1;
      send("JOIN %s\r\n", channel);
      return 0;
  }
  
  int IRC::part(char* channel)
  {
      if (!connected)
          return 1;
      send("PART %s\r\n", channel);
      return 0;
  }
  
  int IRC::kick(char* channel, char* nick)
  {
      if (!connected)
          return 1;
      send("KICK %s %s\r\n", channel, nick);
      return 0;
  }
  
  int IRC::raw(char* data)
  {
      if (!connected)
          return 1;
      send("%s\r\n", data);
      return 0;
  }
  
  int IRC::kick(char* channel, char* nick, char* message)
  {
      if (!connected)
          return 1;
      send("KICK %s %s :%s\r\n", channel, nick, message);
      return 0;
  }
  
  int IRC::mode(char* channel, char* modes, char* targets)
  {
      if (!connected)
          return 1;
      if (!targets)
          send("MODE %s %s\r\n", channel, modes);
      else
          send("MODE %s %s %s\r\n", channel, modes, targets);
      return 0;
  }
  
  int IRC::mode(char* modes)
  {
      if (!connected)
          return 1;
      mode(cur_nick, modes, 0);
      return 0;
  }
  
  int IRC::nick(char* newnick)
  {
      if (!connected)
          return 1;
      send("NICK %s\r\n", newnick);
      return 0;
  }
  
  char* IRC::current_nick()
  {
      return cur_nick;
  }
  