/* This is part of Ari's Yahoo Client.
 * This file and Ari's Yahoo Client are copyright (C) 1999 Ari Johnson.
 * For more copyright and licensing information, see the file
 * 'COPYING', which should have been distributed with Ari's Yahoo Client.
 *
 * Yahoo Messenger interface code provided by yahoolib, as provided
 * with GTKYahoo.
 */

#include "config.h"

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <ctype.h>
#include <stdarg.h>
#include <string.h>

#ifdef USE_CURSES
#include <curses.h>
#endif

#include "libyahoo.h"
#include "libyahoo-proto.h"

struct statusrec {
  char *id;
  int status;
  int in_pager;
  int in_chat;
  struct statusrec *next;
};

int debug_packets = 0;
int quit_flag;
struct statusrec *statushead = NULL;
struct yahoo_options options;
struct yahoo_context *context;
char *username;
char *password;
char *cur_target = NULL;
char *prev_target = NULL;
char *reply_target = NULL;
int cur_status = YAHOO_STATUS_AVAILABLE;
int cur_target_changed = 1;
int cur_status_changed = 1;

#ifdef USE_CURSES
WINDOW *win;
WINDOW *displaywin;
WINDOW *sepwin;
WINDOW *inputwin;
#endif

void main_loop(void);
void process_input(void);
void handle_io(void);
int is_integer(char *);
void myprintf(char *, ...);

#ifdef USE_CURSES
void separatewin(char *, char *);
void init_curses(void);
#else
void show_prompt(void);
#endif

int main(int argc, char *argv[]) {
  int i;
  struct yahoo_buddy *tmpbuddy;
  struct statusrec *tmprec;
  char *id;
  char *tmp;

  if(argc > 3) {
    fprintf(stderr, "Usage:  %s [<username>] [<password>]\n", argv[0]);
    return 1;
  }

  if(argc == 3) {
    username = (char *) malloc(strlen(argv[1]) + 1);
    strncpy(username, argv[1], strlen(argv[1]));
    password = (char *) malloc(strlen(argv[2]) + 1);
    strncpy(password, argv[2], strlen(argv[2]));
  } else if(argc == 2) {
    username = (char *) malloc(strlen(argv[1]) + 1);
    strncpy(username, argv[1], strlen(argv[1]));
    tmp = getpass("Password: ");
    password = (char *) malloc(strlen(tmp) + 1);
    strncpy(password, tmp, strlen(tmp));
  } else if(argc == 1) {
    username = (char *) malloc(100);
    printf("Yahoo ID: ");
    fflush(stdout);
    scanf("%s", username);
    tmp = getpass("Password: ");
    password = (char *) malloc(strlen(tmp) + 1);
    strncpy(password, tmp, strlen(tmp));
  }

  options.connect_mode = YAHOO_CONNECT_NORMAL;

  context = yahoo_init(username, password, &options);

  if(!context) {
    fprintf(stderr, "Connection failed.\n");
    return 1;
  }

  if(!yahoo_connect(context)) {
    fprintf(stderr, "Connection failed.\n");
    return 1;
  }

  yahoo_get_config(context);

  yahoo_cmd_logon(context, cur_status);

  i = 0;
  while(context->buddies && context->buddies[i])
    i++;
  i--;
  while(i >= 0) {
    tmpbuddy = context->buddies[i];
    id = tmpbuddy->id;

    tmprec = (struct statusrec *) malloc(sizeof(struct statusrec));
    tmprec->id = id;
    tmprec->status = cur_status;
    tmprec->in_pager = 0;
    tmprec->in_chat = 0;
    tmprec->next = statushead;
    statushead = tmprec;
    
    i--;
  }

#ifdef USE_CURSES
  init_curses();
#endif

  myprintf("Thank you for using Ari's Yahoo Client.\n");
  myprintf("Ari's Yahoo Client is copyright (C) 2000 Ari Johnson\n");
  myprintf("and is covered by the GNU General Public License.\n\n");
  myprintf("Yahoo Messenger interface provided by yahoolib,\n");
  myprintf("as distributed with GTKYahoo.\n\n");

  main_loop();

  free(username);
  free(password);
  return 0;
}

struct statusrec *get_statusrec(char *id) {
  struct statusrec *ptr;

  ptr = statushead;
  while(ptr && strcasecmp(id, ptr->id))
    ptr = ptr->next;

  return ptr;
}

int get_in_pager(char *id) {
  struct statusrec *rec;

  rec = get_statusrec(id);
  if(!rec)
    return 0;

  return rec->in_pager;
}

int get_in_chat(char *id) {
  struct statusrec *rec;

  rec = get_statusrec(id);
  if(!rec)
    return 0;

  return rec->in_chat;
}

int get_status(char *id) {
  struct statusrec *rec;

  rec = get_statusrec(id);
  if(!rec)
    return YAHOO_STATUS_AVAILABLE;

  return rec->status;
}

void set_in_pager(char *id, int in) {
  struct statusrec *rec;

  rec = get_statusrec(id);
  if(!rec)
    return;

  rec->in_pager = in;
}

void set_in_chat(char *id, int in) {
  struct statusrec *rec;

  rec = get_statusrec(id);
  if(!rec)
    return;

  rec->in_chat = in;
}

void set_status(char *id, int status) {
  struct statusrec *rec;

  rec = get_statusrec(id);
  if(!rec)
    return;

  rec->status = status;
}

void show_userlist(void) {
  int i;
  struct yahoo_buddy *tmpbuddy;
  char *id;
  struct statusrec *rec;
  int status, in_pager, in_chat;

  i = 0;
  while(context->buddies && context->buddies[i]) {
    tmpbuddy = context->buddies[i];
    id = tmpbuddy->id;

    status = get_status(id);
    in_pager = get_in_pager(id);
    in_chat = get_in_chat(id);

    if(!in_pager && !in_chat)
      myprintf("   %s\n", id);
    i++;
  }

  i = 0;
  while(context->buddies && context->buddies[i]) {
    tmpbuddy = context->buddies[i];
    id = tmpbuddy->id;

    status = get_status(id);
    in_pager = get_in_pager(id);
    in_chat = get_in_chat(id);

    if((in_pager || in_chat) && status != YAHOO_STATUS_AVAILABLE)
      myprintf("*  %s [%s]\n", id, yahoo_get_status_string(status));
    i++;
  }

  i = 0;
  while(context->buddies && context->buddies[i]) {
    tmpbuddy = context->buddies[i];
    id = tmpbuddy->id;

    status = get_status(id);
    in_pager = get_in_pager(id);
    in_chat = get_in_chat(id);

    if((in_pager || in_chat) && status == YAHOO_STATUS_AVAILABLE)
      myprintf("*  %s\n", id);
    i++;
  }
}

int do_add(char *id) {
  int i;
  struct yahoo_buddy *tmpbuddy;
  char *group;

  group = NULL;
  if(context->buddies && context->buddies[0]) {
    tmpbuddy = context->buddies[0];
    group = tmpbuddy->group;
  }


  if(group) {
    yahoo_add_buddy(context, id, username, group, "");
    return 0;
  }

  return 1;
}

int do_remove(char *id) {
  int i;
  struct yahoo_buddy *tmpbuddy;
  char *group;

  group = NULL;
  i = 0;
  while(context->buddies && context->buddies[i]) {
    tmpbuddy = context->buddies[i];
    if(!strcmp(tmpbuddy->id, id)) {
      group = tmpbuddy->group;
    }
    i++;
  }

  if(group) {
    yahoo_remove_buddy(context, id, username, group, "");
    return 0;
  }

  return 1;
}

#ifdef USE_CURSES
void init_curses(void) {
  int maxx, maxy;

  win = initscr();
  cbreak();
  noecho();

  getmaxyx(win, maxy, maxx);

  inputwin = derwin(win, 3, maxx, maxy - 3, 0);
  sepwin = derwin(win, 1, maxx, maxy - 4, 0);
  displaywin = derwin(win, maxy - 4, maxx, 0, 0);

  scrollok(inputwin, TRUE);
  keypad(inputwin, TRUE);
  nodelay(inputwin, TRUE);
  scrollok(displaywin, TRUE);

  separatewin(NULL, NULL);

  refresh();
  wrefresh(inputwin);
}

void separatewin(char *s, char *t) {
  int i, j, maxx, maxy, slen;
  char *ptr;
  char buff[1024];

  getmaxyx(sepwin, maxy, maxx);

  for(i = 0; i < 5; i++)
    buff[i] = '-';

  slen = 5;

  if(s) {
    slen = 7 + strlen(s);
    ptr = s;
    buff[i++] = '<';
    for(; i < maxx && *ptr; i++)
      buff[i] = *ptr++;
    buff[i++] = '>';
  }

  for(j = 0; j < (29 - slen); j++)
    buff[i++] = '-';

  if(t) {
    ptr = t;
    buff[i++] = '<';
    for(; i < maxx && *ptr; i++)
      buff[i] = *ptr++;
    buff[i++] = '>';
  }

  for(; i < (maxx - 6); i++)
    buff[i] = '-';

  if(i <= (maxx - 6)) {
    buff[i++] = '1';
    buff[i++] = '.';
    buff[i++] = '1';
    buff[i++] = '0';
    buff[i++] = '-';
    buff[i++] = '-';
  }

  buff[i] = '\0';

  wmove(sepwin, 0, 0);
  waddstr(sepwin, buff);
  wrefresh(sepwin);
  wrefresh(inputwin);
}
#else
void show_prompt(void) {
  if(cur_status != YAHOO_STATUS_AVAILABLE)
    printf("[%s] ", yahoo_get_status_string(cur_status));
  printf("%s:  ", cur_target ? cur_target : "<none>");
  fflush(stdout);
}
#endif

void main_loop(void) {
  fd_set inset;

  quit_flag = 0;

  myprintf("Connected.\n");

#ifndef USE_CURSES
  show_prompt();
#endif

  while(!quit_flag) {
    FD_ZERO(&inset);
    FD_SET(fileno(stdin), &inset);
    FD_SET(context->sockfd, &inset);

    if(select(context->sockfd + 1, &inset, NULL, NULL, NULL) < 0) {
      perror("select");
      return;
    }

    if(FD_ISSET(fileno(stdin), &inset)) {
      process_input();
#ifdef USE_CURSES
      wrefresh(inputwin);
#endif
    }
    if(FD_ISSET(context->sockfd, &inset)) {
      handle_io();
#ifdef USE_CURSES
      wrefresh(displaywin);
      wrefresh(inputwin);
#endif
    }

    if(cur_status_changed || cur_target_changed) {
#ifdef USE_CURSES
      separatewin((cur_status != YAHOO_STATUS_AVAILABLE)
			? yahoo_get_status_string(cur_status) : NULL,
			cur_target);
#endif
      cur_status_changed = 0;
      cur_target_changed = 0;
    }
  }

  yahoo_cmd_logoff(context);

#ifdef USE_CURSES
  endwin();
#endif
}

void process_command(char *orig) {
  char *cmd;
  char *args;

  if(!orig || !*orig)
    return;

  if(orig[0] == '/') {
    cmd = &orig[1];
    args = strchr(cmd, ' ');
    if(args)
      while(*args == ' ')
        *args++ = '\0';

    if(!strcasecmp(cmd, "help")) {
      myprintf("Ari's Yahoo Client Help\n");
      myprintf("--------------------------------------\n");
      myprintf("Commands:\n");
      myprintf("  /help            Display this screen.\n");
#ifdef USE_CURSES
      myprintf("  /clear           Clear the screen.\n");
      myprintf("  /c               Alias for /clear.\n");
#endif
      myprintf("  /who             Display friends list.\n");
      myprintf("  /w               Alias for /who.\n");
      myprintf("  /msg <ID> <msg>  Send <msg> to <ID>.\n");
      myprintf("  /m <ID> <msg>    Alias for /msg.\n");
      myprintf("  /query <ID>      Select <ID> as target.\n");
      myprintf("  /query           Unselect target.\n");
      myprintf("  /q [<ID>]        Alias for /query.\n");
      myprintf("  /prev            Select previous target.\n");
      myprintf("  /p               Alias for /prev.\n");
      myprintf("  /reply <msg>     Reply to last message.\n");
      myprintf("  /r <msg>         Alias for /reply.\n");
      myprintf("  /statuslist      Lists possible statuses.\n");
      myprintf("  /status <s>      Set status to <s>.\n");
      myprintf("  /add <ID>        Add a friend.\n");
      myprintf("  /remove <ID>     Remove a friend.\n");
      myprintf("  /quit            Exit the program.\n");
      myprintf("--------------------------------------\n");
#ifdef USE_CURSES
    } else if(!strcasecmp(cmd, "clear") || !strcasecmp(cmd, "c")) {
      wclear(displaywin);
      wmove(displaywin, 0, 0);
      wrefresh(displaywin);
#endif
    } else if(!strcasecmp(cmd, "who") || !strcasecmp(cmd, "w")) {
      show_userlist();
    } else if(!strcasecmp(cmd, "msg") || !strcasecmp(cmd, "m")) {
      char *target;
      char *message;
      target = args;
      if(!target) {
        myprintf("You must supply a target and a message.\n");
        return;
      }
      while(*target == ' ')
        *target++ = '\0';
      message = strchr(target, ' ');
      if(!message) {
        myprintf("You must supply a target and a message.\n");
        return;
      }
      while(*message == ' ')
        *message++ = '\0';
      yahoo_cmd_msg(context, username, target, message);
      myprintf("-> %s: %s\n", target, message);
    } else if(!strcasecmp(cmd, "query") || !strcasecmp(cmd, "q")) {
      if(cur_target) {
        if(prev_target)
          free(prev_target);
        prev_target = (char *) malloc(strlen(cur_target) + 1);
        strncpy(prev_target, cur_target, strlen(cur_target));
        prev_target[strlen(cur_target)] = '\0';
        free(cur_target);
        cur_target = NULL;
        cur_target_changed = 1;
      }
      if(args && *args) {
        cur_target = (char *) malloc(strlen(args) + 1);
        strncpy(cur_target, args, strlen(args));
        cur_target[strlen(args)] = '\0';
        cur_target_changed = 1;
      }
    } else if(!strcasecmp(cmd, "prev") || !strcasecmp(cmd, "p")) {
      if(!prev_target) {
        myprintf("No previous target.\n");
      } else {
        char *tmp;
        tmp = prev_target;
        prev_target = cur_target;
        cur_target = tmp;
        cur_target_changed = 1;
      }
    } else if(!strcasecmp(cmd, "reply") || !strcasecmp(cmd, "r")) {
      if(!reply_target) {
        myprintf("No target to which to reply.\n");
      } else if(!args || !*args) {
        myprintf("No message.\n");
      } else {
        yahoo_cmd_msg(context, username, reply_target, args);
        myprintf("-> %s: %s\n", reply_target, args);
      }
    } else if(!strcasecmp(cmd, "statuslist")) {
      int i;
      for(i = 0; i <= YAHOO_STATUS_IDLE; i++)
        if(i != YAHOO_STATUS_CUSTOM && yahoo_get_status_string(i))
          myprintf("%8d: %s\n", i, yahoo_get_status_string(i));
      myprintf("<string>: Custom message\n");
    } else if(!strcasecmp(cmd, "status")) {
      if(args && *args) {
        int newstatus;
        if(!is_integer(args)) {
          yahoo_cmd_set_away_mode(context, YAHOO_STATUS_CUSTOM, args);
          cur_status = YAHOO_STATUS_CUSTOM;
          cur_status_changed = 1;
        } else {
          newstatus = atoi(args);
          if(yahoo_get_status_string(newstatus)) {
            if(newstatus == YAHOO_STATUS_AVAILABLE)
              yahoo_cmd_set_back_mode(context, newstatus, NULL);
            else
              yahoo_cmd_set_away_mode(context, newstatus, NULL);
            cur_status = newstatus;
            cur_status_changed = 1;
          } else {
            myprintf("Invalid status number.\n");
          }
        }
      } else {
        cur_status = YAHOO_STATUS_AVAILABLE;
        yahoo_cmd_set_back_mode(context, cur_status, NULL);
        cur_status_changed = 1;
      }
    } else if(!strcasecmp(cmd, "add")) {
      if(args && *args) {
        if(do_add(args))
          myprintf("Could not add friend.\n");
        else
          myprintf("Added `%s' to friends list.\n", args);
      } else {
        myprintf("You must give a Yahoo ID to add to your list.\n");
      }
    } else if(!strcasecmp(cmd, "remove")) {
      if(args && *args) {
        if(do_remove(args))
          myprintf("Could not remove friend.\n");
        else
          myprintf("Removed `%s' from friends list.\n", args);
      } else {
        myprintf("You must give a Yahoo ID to remove from your list.\n");
      }
    } else if(!strcasecmp(cmd, "quit")) {
      quit_flag = 1;
    } else {
      myprintf("Invalid command.\n");
    }
  } else {
    if(!cur_target) {
      myprintf("No target selected.\n");
      return;
    }
    yahoo_cmd_msg(context, username, cur_target, orig);
    myprintf("-> %s: %s\n", cur_target, orig);
  }
}

#ifdef USE_CURSES
void process_input(void) {
  static char buff[1024];
  static char *ptr = buff;
  int c;

  c = wgetch(inputwin);
  while(c != ERR) {
    switch(c) {
      case '\n':
      case '\r':
        *ptr = '\0';
        ptr = buff;
        waddch(inputwin, '\n');
        wrefresh(inputwin);
        process_command(buff);
        wrefresh(displaywin);
        break;
      case '\b':
      case 127:
        if(ptr > buff) {
          ptr--;
          waddch(inputwin, '\b');
          waddch(inputwin, ' ');
          waddch(inputwin, '\b');
        }
        break;
      default:
        *ptr++ = c;
        waddch(inputwin, c);
    }

    c = wgetch(inputwin);
  }
}
#else
void process_input(void) {
  char buff[1024];
  char *ptr;

  fgets(buff, 1024, stdin);
  ptr = strchr(buff, '\n');
  if(ptr)
    *ptr = '\0';
  ptr = strchr(buff, '\r');
  if(ptr)
    *ptr = '\0';
  process_command(buff);
  show_prompt();
}
#endif

void process_packet_status(struct yahoo_packet *pkt) {
  int i;

  if(pkt->service == YAHOO_SERVICE_LOGOFF &&
     !strcmp(pkt->active_id, username)) {
    myprintf("Disconnected.\n");
    quit_flag = 1;
  }

  for(i = 0; i < pkt->idstatus_count; i++) {
    struct yahoo_idstatus *rec;
    char *id;

    rec = pkt->idstatus[i];
    id = rec->id;

    set_in_pager(id, rec->in_pager);
    set_in_chat(id, rec->in_chat);
    if(pkt->service != YAHOO_SERVICE_CHATLOGOFF &&
       pkt->service != YAHOO_SERVICE_CHATLOGON) {
      set_status(id, rec->status);
    }

    if(pkt->service == YAHOO_SERVICE_LOGON)
      myprintf("%s has connected.\n", id);
    else if(pkt->service == YAHOO_SERVICE_LOGOFF)
      myprintf("%s has disconnected.\n", id);
    else
      myprintf("%s is now %s.\n", id,
		yahoo_get_status_string(get_status(id)));

#ifndef USE_CURSES
    show_prompt();
#endif
  }
}

void process_packet_message(struct yahoo_packet *pkt) {
  if(pkt->msgtype == YAHOO_MSGTYPE_STATUS)
    set_status(pkt->msg_id, pkt->msg_status);

  if(pkt->msg) {
    if(pkt->msgtype == YAHOO_MSGTYPE_BOUNCE) {
      myprintf("%s -> %s: <Message not sent, user not online.>\n",
		pkt->active_id, pkt->msg_id);
    } else {
      myprintf("%s -> %s: %s\n", pkt->msg_id, pkt->active_id,
		pkt->msg);
      if(reply_target)
        free(reply_target);
      reply_target = (char *) malloc(strlen(pkt->msg_id) + 1);
      strncpy(reply_target, pkt->msg_id, strlen(pkt->msg_id));
      reply_target[strlen(pkt->msg_id)] = '\0';
    }

#ifndef USE_CURSES
    show_prompt();
#endif
  }
}

void process_packet_ping(struct yahoo_packet *pkt) {
  myprintf("Ping.\n");

#ifndef USE_CURSES
  show_prompt();
#endif
}

void handle_io(void) {
  struct yahoo_rawpacket *rawpkt;
  struct yahoo_packet *pkt;

  yahoo_getdata(context);

  if(context->io_buf_curlen <= 103)
    return;

  while((rawpkt = yahoo_getpacket(context))) {
    pkt = yahoo_parsepacket(context, rawpkt);

    if(debug_packets) {
      myprintf("Received packet:\n");
      myprintf("\tService = %s\n", yahoo_get_service_string(pkt->service));
      myprintf("\tReal ID = %s\n", pkt->real_id);
      myprintf("\tActive ID = %s\n", pkt->active_id);
      myprintf("\tConnection ID = %X\n", pkt->connection_id);
      myprintf("\tMagic ID = %X\n", pkt->magic_id);
      myprintf("\tUnknown Flag 1 = %X\n", pkt->unknown1);
      myprintf("\tMessage Type = %X\n", pkt->msgtype);
      myprintf("\tRaw Content = %s\n", rawpkt->content);
    }

    switch(pkt->service) {
      case YAHOO_SERVICE_USERSTAT:
      case YAHOO_SERVICE_CHATLOGON:
      case YAHOO_SERVICE_CHATLOGOFF:
      case YAHOO_SERVICE_LOGON:
      case YAHOO_SERVICE_LOGOFF:
      case YAHOO_SERVICE_ISAWAY:
      case YAHOO_SERVICE_ISBACK:
        process_packet_status(pkt);
        break;
      case YAHOO_SERVICE_MESSAGE:
      case YAHOO_SERVICE_CHATMSG:
      case YAHOO_SERVICE_SYSMESSAGE:
        process_packet_message(pkt);
        break;
      case YAHOO_SERVICE_NEWCONTACT:
        if(pkt->msg)
          process_packet_message(pkt);
        else
          process_packet_status(pkt);
        yahoo_get_config(context);
        break;
      case YAHOO_SERVICE_PING:
        process_packet_ping(pkt);
        break;
    }

    yahoo_free_packet(pkt);
    yahoo_free_rawpacket(rawpkt);
  }
}

int is_integer(char *s) {
  char *ptr;

  if(!s || !*s)
    return 0;

  ptr = s;
  if(((*ptr == '-') || (*ptr == '+')) && ptr[1])
    ptr++;
  while(isdigit(*ptr))
    ptr++;
  if(*ptr)
    return 0;

  return 1;
}

#ifdef USE_CURSES
void myprintf(char *fmt, ...) {
  va_list ap;

  va_start(ap, fmt);
  vwprintw(displaywin, fmt, ap);
  va_end(ap);
}
#else
void myprintf(char *fmt, ...) {
  va_list ap;

  va_start(ap, fmt);
  vprintf(fmt, ap);
  va_end(ap);
}
#endif

