/*
   IRC.java

   Author:       Trolan
   Description:
*/

package net.trolans.IRC;

import java.io.*;
import java.net.*;
import java.text.*;
import java.util.*;

public class IRC
{
   IRCUser me;
   IRCChannel ircChannel;
   IRCServer server;
   IRCFrame frame;
   Launch launch;
   UserListModel ulm;
   boolean useIdent = true;
   boolean inConfigure = true;
   private boolean inWhoOutput = false;
   Properties prefs;
   SimpleDateFormat timestamp;

   FileWriter logWriter;

   static final char CTCP_MARKER   = 0x01; // CTCP
   static final char BOLD          = 0x02; // bold text [deprecated]
   static final char COLOR         = 0x03; // color init [deprecated]
   static final char EOT           = 0x04; // Extended text attrib end marker
   static final char FORMAT_MARKER = 0x06; // Text formatting marker
   static final char BELL          = 0x07; // terminal bell
   static final char RESET         = 0x11; // reset styles [deprecated]
   static final char ITALIC        = 0x18; // italics [deprecated]
   static final char REVERSE       = 0x1A; // Reverse text [deprecated]
   static final char UNDERLINE     = 0x25; // underline [deprecated]

   static final int MSG_NORMAL  = 0;
   static final int MSG_INFO    = 1;
   static final int MSG_NOTICE  = 2;
   static final int MSG_SERVER  = 3;
   static final int MSG_DEBUG   = 4;

   public IRC()
   {
      prefs = new Properties();
      try {
         prefs.load(new BufferedInputStream(new FileInputStream("irc.properties")));
      }
      catch (Exception e) {}

      timestamp = new SimpleDateFormat(prefs.getProperty("irc.timestamp_format", "[HH:mm]"));

      try {
         launch = new Launch(this);
         launch.initComponents();
         launch.setVisible(true);
      }
      catch (Exception e) {
         e.printStackTrace();
      }
      while(inConfigure == true) {}
      try {
         frame = new IRCFrame(this);
         frame.initComponents();
         frame.setVisible(true);
      }
      catch (Exception e) {
         e.printStackTrace();
      }
      if(prefs.getProperty("irc.default.logfile", "").length() > 0)
      {
         try
         {
            logWriter = new FileWriter(prefs.getProperty("irc.default.logfile"), true);
         }
         catch (IOException x) {}
      }
      server.connect(me);
      frame.nickLabel.setText(me.toString());
      frame.textEntry.requestFocus();
      this.ulm = frame.ulm;

      while(server.isConnected())
         handleData();
   }

   // Main entry point
   static public void main(String[] args)
   {
      new IRC();
   }

   public void handleData()
   {
      String data = "";
      boolean newData = false;

      if((data = server.readLine()) != null)
      {
         if(data.startsWith("PING :"))
         {
            server.sendData("PONG :" + data.substring(6));
         }
         else if(data.charAt(0) == ':')
         {
            String in = data.substring(1);
            StringTokenizer st = new StringTokenizer(in);
            if(!st.hasMoreTokens()) return;
            String from   = st.nextToken();
            String type   = st.nextToken().toLowerCase();
            String target = st.nextToken();
            int typeCode;
            IRCUser fromUser;
            try
            {
               typeCode = Integer.parseInt(type);
            }
            catch (NumberFormatException e) { typeCode = 0; }
            String content = "";
            String channel = "";
            String user = "";
            while(st.hasMoreTokens()) { content += st.nextToken() + " "; }
            if(content.length() > 0)
            {
               content = content.substring(0, content.length() - 1);
               /* Rip off leading :'s */
               if(content.charAt(0) == ':')
                  content = content.substring(1);
            }
            if(typeCode > 0)
            {
               switch(typeCode)
               {
                  /* Local replies */
                  case 1: /* RPL_WELCOME */
                  case 2: /* RPL_YOURHOST */
                  case 3: /* RPL_CREATED */
                  case 4: /* RPL_MYINFO */
                      displayOutput(content, MSG_SERVER);
                     break;
                  case 5: /* RPL_PROTOCTL */
                     // FIXME: Not ideal, but it works.  For now.
                     if(prefs.getProperty("irc.default.channel", "").length() > 0)
                     {
                        server.sendData("JOIN " + prefs.getProperty("irc.default.channel", ""));
                     }
                     displayOutput(content, MSG_SERVER);
                     break;
                  case 6: /* RPL_MAP */
                  case 7: /* RPL_MAPEND */
                     displayOutput(content, MSG_SERVER);
                     break;

                  /* Numeric replies */
                  case 251: /* RPL_LUSERCLIENT */
                  case 252: /* RPL_LUSEROP */
                  case 253: /* RPL_LUSERUNKNOWN */
                  case 254: /* RPL_LUSERCHANNELS */
                  case 255: /* RPL_LUSERME */
                  case 265: /* RPL_LOCALUSERS */
                  case 266: /* RPL_GLOBALUSERS */
                     displayOutput(content, MSG_SERVER);
                     break;

                  case 301: /* RPL_AWAY */
                      st = new StringTokenizer(content);
                      user = st.nextToken();
                      content = content.substring(content.indexOf(":") + 1);
                     displayOutput(user + " is away: " + content, MSG_INFO);
                     break;

                  case 305: /* RPL_UNAWAY */
                  case 306: /* RPL_NOWAWAY */
                     content = content.substring(content.indexOf(":") + 1);
                     displayOutput(content, MSG_INFO);
                     break;

                  case 311: /* RPL_WHOISUSER */
                     displayOutput(content, MSG_INFO);
                     break;
                  case 312: /* RPL_WHOISSERVER */
                      st = new StringTokenizer(content);
                      user = st.nextToken();
                     String server = st.nextToken();
                     String servDesc = content.substring(content.indexOf(":") + 1);
                     displayOutput(user + " using " + server + " " + servDesc, MSG_INFO);
                     break;
                  case 313: /* RPL_WHOISOPERATOR */
                     displayOutput(content, MSG_INFO);
                     break;
                  case 315: /* RPL_ENDOFWHO */
                     content = content.substring(content.indexOf(":") + 1);
                     displayOutput(content, MSG_INFO);
                     break;
                  case 317: /* RPL_WHOISIDLE */
                     st = new StringTokenizer(content);
                     long lTmp;
                     long idle;
                     user = st.nextToken();
                     try
                     {
                        idle = Integer.parseInt(st.nextToken());
                        lTmp = Integer.parseInt(st.nextToken());
                     }
                     catch (NumberFormatException x) { idle = 0; lTmp = 0; }
                     Date login = new Date(lTmp * 1000);
                     displayOutput(user + " has been idle " + idle + " seconds, signed on " + login.toString(), MSG_INFO);
                     break;
                  case 318: /* RPL_ENDOFWHOIS */
                     content = content.substring(content.indexOf(":") + 1);
                     displayOutput(content, MSG_INFO);
                     break;
                  case 319: /* RPL_WHOISCHANNELS */
                     st = new StringTokenizer(content);
                     user = st.nextToken();
                     channel = content.substring(content.indexOf(":") + 1);
                     displayOutput(user + " on " +  channel, MSG_INFO);
                     break;
                  case 324: /* RPL_CHANNELMODES */
                     st = new StringTokenizer(content);
                     channel = st.nextToken();
                     String mode = st.nextToken();
                     if(ircChannel != null)
                     {
                        if(channel.equalsIgnoreCase(ircChannel.getName()))
                        {
                           ircChannel.setModes(mode);
                        }
                     }
                     displayOutput(channel + " modes: " + mode, MSG_INFO);
                     break;
                  case 329: /* RPL_CREATIONTIME */
                     st = new StringTokenizer(content);
                     channel = st.nextToken();
                     long creation;
                     try
                     {
                        creation = Integer.parseInt(st.nextToken());
                     }
                     catch (NumberFormatException e) { creation = 0; }
                     if(channel.equalsIgnoreCase(ircChannel.getName()))
                        ircChannel.setCreationTime(creation);
                     Date c = new Date(creation * 1000);
                     displayOutput(channel + " was created on " + c.toString(), MSG_INFO);
                     break;
                  case 332: /* RPL_TOPIC */
                     channel = content.substring(0, content.indexOf(":") - 1);
                     String topic = content.substring(content.indexOf(":") + 1);
                     if(ircChannel != null)
                     {
                        if(channel.equalsIgnoreCase(ircChannel.getName()))
                           ircChannel.setTopic(topic);
                     }
                     displayOutput("TOPIC for " + channel + ": " + topic, MSG_INFO);
                     break;
                  case 333: /* RPL_TOPICWHOTIME */
                     st = new StringTokenizer(content);
                     long time;
                     st.nextToken();
                     String setBy  = st.nextToken();
                     try
                     {
                        time = Integer.parseInt(st.nextToken());
                     }
                     catch (NumberFormatException e) { time = 0; }
                     Date now = new Date(time*1000);
                     displayOutput("set by " + setBy + " at " + now.toString(), MSG_INFO);
                     break;
                  case 352: /* RPL_WHOREPLY */
                     st = new StringTokenizer(content);
                     channel = st.nextToken();
                     String uIdent    = st.nextToken();
                     String uHost     = st.nextToken();
                     String uServer   = st.nextToken();
                     String uNick     = st.nextToken();
                     String uFlags    = st.nextToken();
                     String uHops     = st.nextToken();
                     String uRealName = "";
                     while(st.hasMoreTokens()) { uRealName += st.nextToken() + " "; }
                     if(!inWhoOutput)
                     {
                        displayOutput("* Users on " + channel);
                        inWhoOutput = true;
                     }
                     if(uRealName.length() > 0)
                        uRealName = uRealName.substring(0, uRealName.length() - 1);
                     displayOutput("\t" + uNick + " (" + uIdent + "@" + uHost + ") on " + uServer + " " + uFlags + " (" + uRealName + ")");
                     break;

                  case 353: /* RPL_NAMREPLY */
                     channel = content.substring(2, content.indexOf(":") - 1);
                     String users = content.substring(content.indexOf(":") + 1);

                     displayOutput("Users on channel " + channel + ": " + users, MSG_NOTICE);

                     if(channel.equalsIgnoreCase(ircChannel.getName()))
                     {
                        IRCUser nUser;
                        ulm.removeAllElements();
                        st = new StringTokenizer(users);
                        while(st.hasMoreTokens())
                        {
                           nUser = new IRCUser(st.nextToken());
                           ulm.addUser(nUser);
                           if(nUser.getNick().equals(me))
                           {
                              if(nUser.getPrefix().equals("@"))
                              {
                                 ircChannel.setTopicProtected(false);
                              }
                           }
                        }
                     }
                     break;

                  case 366: /* RPL_ENDOFNAMES */
                     content = content.substring(content.indexOf(":") + 1);
                     displayOutput(content, MSG_SERVER);
                     break;

                  case 372: /* RPL_MOTD */
                  case 375: /* RPL_MOTDSTART */
                  case 376: /* RPL_ENDOFMOTD */
                     displayOutput(content, MSG_SERVER);
                     break;

                  /* 400-599 are errors */
                  case 421: /* ERR_UNKNOWNCOMMAND */
                     displayOutput(content, MSG_SERVER);
                     break;
                  case 422: /* ERR_NOMOTD */
                     displayOutput(content, MSG_SERVER);
                     break;
                  case 433: /* ERR_NICKNAMEINUSE */
                     content = content.substring(content.indexOf(":") + 1);
                     displayOutput(content, MSG_INFO);
                     break;
                  case 438: /* ERR_NCHANGETOOFAST */
                     content = content.substring(content.indexOf(":") + 1);
                     displayOutput(content, MSG_INFO);
                     break;
                  case 482: /* ERR_CHANOPRIVSNEEDED */
                     content = content.substring(content.indexOf(":") + 1);
                     displayOutput(content, MSG_INFO);
                     break;

                  default:
                     displayOutput(data);
                     break;
               }
               return;
            }
            fromUser = new IRCUser(from);
            if(type.equals("privmsg"))
            {
               if(content.charAt(0) == CTCP_MARKER) // CTRL-A
               {
                  String ctcpcontent = "";
                  String action = "";
                  content = content.substring(1, content.length() - 1); // Rip off trailing ^A
                  st = new StringTokenizer(content);
                  action = st.nextToken().toLowerCase();
                  while(st.hasMoreTokens()) { ctcpcontent += st.nextToken() + " "; }
                  if(action.equals("action"))
                  {
                     displayOutput(fromUser + " " + ctcpcontent, MSG_INFO);
                  }
                  else if(action.equals("clientinfo"))
                  {
                     displayOutput("CTCP CLIENTINFO from " + fromUser, MSG_INFO);
                     server.sendData("NOTICE " + fromUser + " :" + CTCP_MARKER + "NONE" + CTCP_MARKER);
                  }
                  else if(action.equals("dcc"))
                  {
                     st = new StringTokenizer(ctcpcontent);
                     String command    = st.nextToken().toLowerCase();
                     String arg   = st.nextToken();
                     String remoteHost = st.nextToken();
                     String remotePort = st.nextToken();
                     if(command.equals("chat"))
                     {
                        displayOutput("DCC CHAT request from " + fromUser + "@" + long2ip(remoteHost) + ":" + remotePort, MSG_INFO);
                     }
                     else if(command.equals("offer"))
                     {
                        String fileSize = st.nextToken();
                        displayOutput("DCC OFFER request from " + fromUser + " (" + long2ip(remoteHost) + ") of file '" + arg + "' (" + fileSize + " bytes)", MSG_INFO);
                     }
                     else if(command.equals("send"))
                     {
                        String fileSize = st.nextToken();
                        displayOutput("DCC SEND request from " + fromUser + " (" + long2ip(remoteHost) + ") of file '" + arg + "' (" + fileSize + " bytes)", MSG_INFO);
                     }
                     else if(command.equals("xmit"))
                     {
                        String fileSize = st.nextToken();
                        displayOutput("DCC XMIT request from " + fromUser + " (" + long2ip(remoteHost) + ") of file '" + arg + "' (" + fileSize + " bytes)", MSG_INFO);
                     }
                     else
                     {
                        displayOutput("Unknown DCC request from " + fromUser + "; command: " + command + "; ctcpcontent: " + ctcpcontent, MSG_INFO);
                     }
                  }
                  else if(action.equals("ping"))
                  {
                     displayOutput("CTCP PING from " + fromUser, MSG_INFO);
                     server.sendData("NOTICE " + fromUser + " :" + CTCP_MARKER + "PONG " + ctcpcontent + CTCP_MARKER);
                  }
                  else if(action.equals("time"))
                  {
                     displayOutput("CTCP TIME from " + fromUser, MSG_INFO);
                     server.sendData("NOTICE " + fromUser + " :" + CTCP_MARKER + "RFC822 Time Format <HERE>" + CTCP_MARKER);
                  }
                  else if(action.equals("version"))
                  {
                     displayOutput("CTCP VERSION from " + fromUser, MSG_INFO);
                     server.sendData("NOTICE " + fromUser + " :" + CTCP_MARKER + "TroIRC (Java) 0.5 http://trolans.net/projects/IRC/" + CTCP_MARKER);
                  }
                  else if(action.equals("userinfo"))
                  {
                     displayOutput("CTCP USERINFO from " + fromUser, MSG_INFO);
                     server.sendData("NOTICE " + fromUser + " :" + CTCP_MARKER + "Nosy bastard" + CTCP_MARKER);
                  }
                  else
                  {
                     displayOutput("Unknown CTCP: " + action + " from " + fromUser + "; " + ctcpcontent, MSG_DEBUG);
                  }
               }
               else if(target.equalsIgnoreCase(me.getNick()))
               {
                  displayOutput("*" + fromUser + "* " + content);
               }
               else
               {
                  displayOutput("<" + fromUser + "> " + content);
               }
            }
            else if(type.equals("auth"))
            {
               displayOutput(content);
            }
            else if(type.equals("join"))
            {
               if(target.charAt(0) == ':')
                  target= target.substring(1);
               displayOutput("JOIN " + fromUser + " (" + fromUser.getUserHost() + ") has joined " + target, MSG_NOTICE);
               if(!fromUser.equals(me))
               {
                  ulm.addUser(fromUser);
               }
               else
               {
                  ircChannel = new IRCChannel(frame, target);
               }
            }
            else if(type.equals("kick"))
            {
               st = new StringTokenizer(content);
               String kickee = st.nextToken();
               content = content.substring(kickee.length() + 2);
               displayOutput("KICK " + kickee + " off of " + target + " by " + fromUser + " (" + content + ")", MSG_NOTICE);
               if(me.equals(kickee))
               {
                  ulm.removeAllElements();
                  frame.channelLabel.setText("");
                  ircChannel = null;
               }
               ulm.removeUser(kickee);
            }
            else if(type.equals("kill"))
            {
               st = new StringTokenizer(content);
               String killed = st.nextToken();
               displayOutput("KILL: " + content, MSG_DEBUG);
               if(me.equals(killed))
               {
                  ulm.removeAllElements();
                  ircChannel = null;
                  server = null;
               }
               ulm.removeUser(killed);
            }
            else if(type.equals("mode"))
            {
               st = new StringTokenizer(content);
               String modes = st.nextToken();
               boolean enable = (modes.charAt(0) == '+') ? true:false;
               modes = modes.substring(1);

               displayOutput(fromUser + " sets MODE " + content + " on " + target, MSG_INFO);
               if(content.charAt(0) != '#')
               {
                  String nUser;
                  char flag = ' ';
                  for(int x = 0; x < modes.length(); x++)
                  {
                     if(modes.charAt(x) == 'v')
                        flag = '+';
                     else if(modes.charAt(x) == 'h')
                        flag = '%';
                     else if(modes.charAt(x) == 'o')
                        flag = '@';
                     else
                        flag = ' ';
                     // Server modes don't hit this
                     if(st.hasMoreTokens())
                     {
                        nUser = st.nextToken();
                        ulm.updateUser(nUser, flag, enable);
                        if(nUser.equalsIgnoreCase(me.getNick()) && (flag == '@'))
                        {
                           ircChannel.setTopicProtected(false);
                        }
                     }
                  }
                  // Clean this up
                  frame.userList.repaint();
               }
               else
               {
                  if(content.equalsIgnoreCase(ircChannel.getName()))
                  {
                     ircChannel.setModes(modes);
                  }
               }
            }
            else if(type.equals("nick"))
            {
               if(target.charAt(0) == ':')
                  target= target.substring(1);
               displayOutput(fromUser + " has changed their nick to " + target, MSG_INFO);
               if(fromUser.equals(me))
               {
                  me.setNick(target);
                  frame.nickLabel.setText(me.toString());
               }
               ulm.updateUser(fromUser.getNick(), target);
            }
            else if(type.equals("notice"))
            {
               if(from.indexOf("!") > -1)
               {
                  from = fromUser.getNick();
               }
               displayOutput("-" + from + "- " + content);
            }
            else if(type.equals("part"))
            {
               content = target;
               displayOutput("PART " + fromUser + " (" + fromUser.getUserHost() + ") has left channel " + content, MSG_NOTICE);
               ulm.removeUser(fromUser);
               if(fromUser.equals(me))
               {
                  ulm.removeAllElements();
                  // Clean this up
                  frame.channelLabel.setText("");
                  frame.topicField.setText("");
                  ircChannel = null;
               }
            }
            else if(type.equals("topic"))
            {
               displayOutput("TOPIC on " + target + " changed to '" + content + "' by " + fromUser, MSG_INFO);
               if(target.equalsIgnoreCase(ircChannel.getName()))
               {
                  ircChannel.setTopic(content);
               }
            }
            else if(type.equals("quit"))
            {
               displayOutput("QUIT " + fromUser + " (" + fromUser.getUserHost() + ") has quit (" + content + ")", MSG_NOTICE);
               ulm.removeUser(fromUser);
            }
            else
            {
               displayOutput(data);
            }
         }
         else
         {
            displayOutput(data);
         }
      }
   }

   public void handleCommands(String input)
   {
      if(input.indexOf("/") == 0)
      {
         StringTokenizer st = new StringTokenizer(input.substring(1));
         if(!st.hasMoreTokens()) return;
         String command = st.nextToken().toLowerCase();
         String content = "";

         while(st.hasMoreTokens()) { content += st.nextToken() + " "; }

         if(content.length() > 0)
            content = content.substring(0, content.length() - 1);

         if(command.equals("away"))
         {
            server.sendData("AWAY :" + content);
         }
         else if(command.equals("exit"))
         {
            server.sendData("QUIT :" + content);
            server.disconnect();
         }
         else if(command.equals("join"))
         {
            server.sendData("JOIN " + content);
            /* Get channel creation/modes */
            server.sendData("MODE " + content);
         }
         else if(command.equals("kick"))
         {
            if(content.length() > 0)
            {
               st = new StringTokenizer(content);
               String user = st.nextToken();
               try
               {
                  content = content.substring(user.length() + 1);
               }
               catch(Exception e) { content = "*kick!*"; }
               server.sendData("KICK " + ircChannel.getName() + " " + user + " :" + content);
            }
         }
         else if(command.equals("me"))
         {
            server.sendData("PRIVMSG " + ircChannel.getName() + " :" + CTCP_MARKER + "ACTION " + content + CTCP_MARKER);
            displayOutput("* " + me.getNick() + " " + content);
         }
         else if(command.equals("mode"))
         {
            server.sendData("MODE " + content);
         }
         else if(command.equals("msg"))
         {
            st = new StringTokenizer(content);
            String toNick = st.nextToken();
            content = "";
            while(st.hasMoreTokens()) { content += st.nextToken() + " "; }
            if(content.length() > 0)
               content = content.substring(0, content.length() - 1);
            server.sendData("PRIVMSG " + toNick + " :" + content);
            displayOutput("-> *" + toNick + "* " + content);
         }
         else if(command.equals("nick"))
         {
            server.sendData("NICK " + content);
         }
         else if(command.equals("part"))
         {
            if(content.length() == 0)
            {
               content = ircChannel.getName();
            }
            server.sendData("PART " + content);
         }
         else if(command.equals("quit"))
         {
            server.sendData("QUIT :" + content);
         }
         else if(command.equals("raw"))
         {
            server.sendData(content);
            displayOutput("sending: \"" + content + "\"", MSG_DEBUG);
         }
         else if(command.equals("server"))
         {
            if(server.isConnected())
            {
               server.disconnect();
               ircChannel = null;
               ulm.removeAllElements();
            }
            server.setServer(content);
            server.connect(me);
         }
         else if(command.equals("topic"))
         {
            server.sendData("TOPIC " + ircChannel.getName() + " :" + content);
         }
         else if(command.equals("who"))
         {
            server.sendData("WHO " + content);
         }
         else if(command.equals("whois"))
         {
            server.sendData("WHOIS " + content);
         }
         else
         {
            server.sendData(input.substring(1));
         }
      }
      else
      {
         if(ircChannel != null)
         {
            server.sendData("PRIVMSG " + ircChannel.getName() + " :" + input);
            displayOutput("<" + me.getNick() + "> " + input);
         }
         else
         {
            displayOutput("You are not joined to a channel!", MSG_NOTICE);
         }
      }
   }

   public void displayOutput(String out) { this.displayOutput(out, MSG_NORMAL); }

   public void displayOutput(String out, int flag)
   {
      switch(flag)
      {
         case MSG_NOTICE:
         case MSG_SERVER:
            out = "*** " + out;
            break;
         case MSG_INFO:
            out = "* " + out;
            break;
         case MSG_DEBUG:
            out = "*** DEBUG: " + out;
            break;
         case MSG_NORMAL:
         default:
            break;
      }

      //if(prefs.getProperty("irc.default.timestamp").equals("true"))
      //{
      if(flag < MSG_SERVER)
         out = timestamp.format(new Date()) + " " + out;
      //}

      // Output text processing would go here
      frame.channelWindow.append("\n" + out);
      frame.channelWindow.setCaretPosition(frame.channelWindow.getText().length());
      if(logWriter != null)
      {
         try
         {
            logWriter.write(out + "\n");
            logWriter.flush();
         }
         catch (IOException x) {}
      }
   }

   public String long2ip(String ip)
   {
      try
      {
         long tmp = Integer.parseInt(ip);
         long o1 = (tmp & 0xFF000000) >> 24;
         long o2 = (tmp & 0x00FF0000) >> 16;
         long o3 = (tmp & 0x0000FF00) >>  8;
         long o4 = tmp & 0x000000FF;
         return (o1 + "." + o2 + "." + o3 + "." + o4);
      }
      catch (Exception e) { return "<malformed IP>"; }
   }
}
