/**************************************************************
 * FFTacticsMUD : desc.cpp                                    *
 **************************************************************
 * (c) 2002 Damien Dailidenas (Trenton). All rights reserved. *
 **************************************************************/

#include "main.h"
#include <strstream>
#include <cstdio>
#include <cerrno>
#include <unistd.h>
#include "mysql/mysql.h"

bool wizlock, newlock;

DESC::DESC() {
  snoop_by=NULL;
  ch=NULL;
  fcommand=0;
  desc=0;
  connected=0;
  repeat=0;
  timer=0;
  connected=CON_GET_NAME;
  next=desc_list;
  desc_list=this;
  return;
}
 
DESC::~DESC() {
  if(this == desc_list)
    desc_list = next;
  else {
    for(DESC *d = desc_list; d; d = d->next) {
      if(d->next == this) {
        d->next = next;
        break;
      }
    }
  }
 
  return;
}

bool DESC::load_ch(const string name) {
  CH *ch = new CH;
  bool found = false;
  MYSQL mysql;
  MYSQL_RES *res;
  MYSQL_ROW row;
  this->ch = ch;
  ch->desc = this;
  ch->name = name;
  ch->confirm_delete = false;
  mysql_init(&mysql);

  if(!mysql_real_connect(&mysql, DB_LOC, DB_USER, DB_PASS, DB_NAME, 0, NULL, 0))
    return false;

  ostrstream ost;
  ost << "SELECT * FROM players WHERE name='" << name << "'" << ends;
  mysql_query(&mysql, ost.str());
  res = mysql_store_result(&mysql);

  if(mysql_num_rows(res)) {
    found = true;
    row = mysql_fetch_row(res);
    short x = 1;
    ch->pwd = row[x];
    ch->email = row[++x];
    ch->birthday[0] = atoi(row[(x += 3)]);
    ch->birthday[1] = atoi(row[++x]);
    ch->HPP = atof(row[++x]);
    ch->MPP = atof(row[++x]);  
    ch->SpP = atof(row[++x]);
    ch->PAP = atof(row[++x]);
    ch->MAP = atof(row[++x]);
    ch->money = atoi(row[++x]);
    ch->sex = atoi(row[++x]);
    ch->cjob = atoi(row[++x]);
    ch->lvl = atoi(row[++x]);
    ch->exp = atoi(row[++x]);
    ch->HP[0] = atoi(row[++x]);
    ch->HP[1] = atoi(row[++x]);
    ch->MP[0] = atoi(row[++x]);
    ch->MP[1] = atoi(row[++x]);
    ch->PA = atoi(row[++x]);
    ch->MA = atoi(row[++x]);
    ch->Sp = atoi(row[++x]);
    ch->Mv = atoi(row[++x]);  
    ch->Ju = atoi(row[++x]);
    ch->Ev = atoi(row[++x]);
    ch->Br = atoi(row[++x]);
    ch->Fa = atoi(row[++x]);
    ch->area = get_area(atoi(row[++x]));
    ch->mins_total = atol(row[++x]);
    ch->mins_ave = atoi(row[++x]);
    ch->mins_login = atoi(row[++x]);
    ch->disabled = atoi(row[++x]);
  }

  mysql_free_result(res);
  mysql_close(&mysql);
  ch->load_jobs();
  ch->load_skills();
  ch->load_objects();
  ch->load_ignored();
  ch->admin = ch->is_admin();
  ch->restore();
  return found;
}

void DESC::close_socket() {
  if(!outbuf.empty())
    process_output(false);
 
  if(snoop_by)
    snoop_by->printf("Your victim has left the game.\n\r");
 
  for(DESC *d = desc_list; d; d = d->next)
    if(d->snoop_by == this)
      d->snoop_by = NULL;

  if(ch) {
    if(connected == CON_PLAYING) {
      log_string(ch->name + " has lost " + ch->his_her() + " link.");
      wiz_echo("wiznet> links> " + ch->name + " has lost " + ch->his_her() + " link.\n\r", WIZ_LINKS);
      ch->desc = NULL;
    }
    else
      zap(ch);
  }

  close(desc);
  DESC *d_this = this;
  zap(d_this);
  return;
}

bool DESC::read() {
  if(!incomm.empty())
    return true;

  if(inbuf.length() > MIL) {
    log_string(host + " input overflow!");
    return false;
  }

  char c_temp[MIL];

  for(;;) {
    int nRead = ::read(desc, c_temp, MIL);

    if(nRead > 0) {
      c_temp[nRead] = '\0';
      inbuf += c_temp;

      if(inbuf.find('\n') != string::npos || inbuf.find('\r') != string::npos)
        break;
    }
    else if(!nRead) {
      log_string("EOF encountered on read.");
      return false;
    }
    else if(errno == EWOULDBLOCK)
      break;
    else {
      perror("DESC::read()");
      return false;
    }
  }

  return true;
}

void DESC::read_buffer() {
  if(!incomm.empty() || inbuf.empty())
    return;

  if(inbuf.find('\n') == string::npos)
    return;

  string::size_type pos;

  if((pos = inbuf.find('\r')) != string::npos)
    inbuf.erase(pos, 1);

  if(inbuf.length() > MIL) {
    write("Ling too long.\n\r");
    inbuf = inbuf.substr(0, MIL - 1);
    inbuf += '\n';
  }

  incomm = inbuf.substr(0, (pos = inbuf.find('\n')));
  inbuf.erase(0, pos + 1);
//  printf("'" + incomm + "' '" + inbuf + "'\n\r");

  if(incomm.empty())
    incomm = " ";

  if((incomm[0] != '!' && incomm != inlast) || !ch || ch->admin)
    repeat = 0;
  else {
    repeat++;

    if(repeat == 25) {
      log_string("spam> " + ch->name + ": " + incomm);
      wiz_echo("wiznet> spam> " + ch->name + ": " + incomm + "\n\r", WIZ_SPAM);
    }
/*
    else if(repeat >= 50) {
      log_string("spam> " + ch->name + " disconnected.");
      wiz_echo("wiznet> spam> " + ch->name + " disconnected.\n\r", WIZ_SPAM);
      repeat = 0;
      close_socket();
      return;
    }
*/
  }

  if(incomm[0] == '!')
    incomm = inlast;
  else
    inlast = incomm;

  return;
}

bool DESC::process_output(const bool fPrompt) {
  extern bool game_down;
 
  if(!game_down)
    if(fPrompt && connected == CON_PLAYING)
      ch->prompt();

  if(outbuf.empty())
    return true;

  if(snoop_by) {
    if(ch)
      snoop_by->printf(ch->name);

    snoop_by->printf("> " + outbuf + ENDL);
  }
 
  if(!write(outbuf)) {
    perror("write");
    outbuf.erase(outbuf.begin(), outbuf.end());
    return false;
  }
  else {
    outbuf.erase(outbuf.begin(), outbuf.end());
    return true;
  }
}

void DESC::printf(const string str)  {
  if(!this || str.empty())
    return;

  if(outbuf.empty() && !fcommand)
    outbuf = ENDL;

  outbuf += str;
  return;
}

void DESC::nanny(string argument) {
  bool fOld;
  ostrstream ost;
  string str;
  argument.erase(0, argument.find_first_not_of(' '));
 
  switch(connected) {
  default:
    ost << "Nanny: bad connected " << connected << "." << ends;
    bug(ost.str());
    close_socket();
    return;
 
  case CON_GET_NAME:
    if(argument.empty()) {
      close_socket();
      return;
    }

    argument[0] = UPPER(argument[0]);

    if(!check_name(argument)) {
      printf("login: ERROR: Illegal name, try another.\n\rName: ");
      return;
    }

    fOld = load_ch(argument);

    if(ch->disabled) {
      printf("ERROR: This account has been disabled.\n\r");
      close_socket();
      return;
    }

    if(multiplaying(ch->name))
      return;

    if(reconnect(argument, false))
      fOld = true;
    else {
      if(wizlock && !ch->admin) {
        printf("Temporarily closed...check back later.\n\r");
        close_socket();
        return;
      }
    }
 
    if(fOld || reconnect(argument, false)) {
      printf("Password: ");
      connected = CON_GET_OLD_PASSWORD;
      return;
    }
    else {
      if(newlock) {
        printf("The game is newlocked.\n\r");
        close_socket();
        return;
      }
 
      ch->printf(ENDL + f_str_box("{!IF YOUR NAME CONTAINS VERBS, ADJECTIVES, OR SPELLED OUT NUMBERS, YOUR CHARACTER WILL BE ERASED!{0") + ENDL);
      ch->printf("Create new character " + argument + "? [y/n] ");
      connected = CON_CONFIRM_NEW_NAME;
      return;
    }
 
    break;
 
  case CON_GET_OLD_PASSWORD:
    if(argument != ch->pwd) {
      printf("login: ERROR: Password incorrect.\n\r");
      close_socket();
      return;
    }

    if(playing(ch->name))
      return;

    ch->socket = host;

    if(multiplaying(ch->name))
      return;
 
    if(reconnect(ch->name, true))
      return;

    str = ch->name + "@" + host + " has logged in.";
    log_string(str);
    wiz_echo(str += ENDL);
    ch->socket = host;

    if(ch->pwd.empty())
      printf("Warning! Null password!\n\rType password null [new password] to fix.\n\r");
 
    connected = CON_PLAYING;
    ch->next = ch_list;
    ch_list = ch;
    ch->login = str_time().substr(0, 3) + str_time().substr(11, 5);
    ch->printf(ENDL);
    ch->to(ch->area->room[ROOM_OUTSIDE]);
    ch->print_MOTD();
    ch->check_battle();
    break;
 
  case CON_BREAK_CONNECT:
    switch(argument[0]) {
    case 'y' : case 'Y':
      for(DESC *d_old = desc_list, *d_next; d_old; d_old = d_next) {
        d_next = d_old->next;
 
        if(d_old == this || !d_old->ch || ch->name != d_old->ch->name)
          continue;

        d_old->close_socket();
      }

      ch->socket = host;

      if(reconnect(ch->name, true))
        return;

      printf("Reconnect attempt failed.\n\rName: ");
      connected = CON_GET_NAME;
      break;
 
    case 'n' : case 'N':
      printf("Name: ");
      connected = CON_GET_NAME;
      break;
 
    default:
      printf("Type Y or N? ");
      break;
    }
 
    break;
 
  case CON_CONFIRM_NEW_NAME:
    switch(argument[0]) {
    case 'y': case 'Y':
      ch->printf(ENDL + f_str_box("Choose a password that others won't guess and you won't forget."));
      ch->printf(f_str_box("{!DO NOT FORGET OR SHARE YOUR PASSWORD WITH ANYONE. IF YOU LOSE YOUR PASSWORD YOU LOSE YOUR CHARACTER.{0", false) + ENDL);
      ch->socket = host;
      printf("Enter password: ");
      connected = CON_GET_NEW_PASSWORD;
      break;
 
    case 'n': case 'N':
      printf("\n\rName: ");
      connected = CON_GET_NAME;
      break;
 
    default:
      ch->printf("Create new character? [y/n] ");
      break;
    }
 
    break;
 
  case CON_GET_NEW_PASSWORD:
    if(argument.length() < 5 || argument.length() > 12) {
      ch->printf("login: ERROR: Password must be between 5 and 12 characters long.\n\rEnter password: ");
      return;
    }
 
    if(argument.find('/') != string::npos || argument.find('\\') != string::npos || argument.find("'") != string::npos) {
      ch->printf("login: ERROR: Password contains illegal characters, try again.\n\rEnter password: ");
      return;
    }
 
    ch->pwd = argument;
    printf("Re-enter password: ");
    connected = CON_CONFIRM_NEW_PASSWORD;
    break;
 
  case CON_CONFIRM_NEW_PASSWORD:
    if(argument != ch->pwd) {
      ch->printf("login: ERROR: Passwords don't match.\n\rEnter password: ");
      connected = CON_GET_NEW_PASSWORD;
      return;
    }

    ch->printf(ENDL + f_str_box("Your email address is required to receive news concerning the MUD and most importantly, for password retrieval if you happen to lose or forget your password.") + ENDL);
    ch->printf("What is your email address? ");
    connected = CON_GET_EMAIL;
    break;

  case CON_GET_EMAIL:
    ch->email = argument;
    ch->printf(ENDL + f_str_box("Your sex affects your overall stat growth and in some cases, which items you can equip and which jobs you can be.", true, false));
    ch->printf(f_str_box("{#NOTE: Males make better fighters, females make better magic users.{0", false) + ENDL);
    ch->printf("\t[1] male\n\r\t[2] female\n\r\n\rWhat is your sex? ");
    connected = CON_GET_SEX;
    break;
 
  case CON_GET_SEX:
    ch->sex = atoi(argument.c_str());

    if(ch->sex != 1 && ch->sex != 2) {
      ch->printf("sex: ERROR: Invalid sex.\n\rWhat is your sex? ");
      break;
    }
 
    --ch->sex;
    ch->printf(ENDL + f_str_box("Your birthday determines your zodiac sign which affects several relationships between you and another character, such as hit percentage and magic damage."));

    for(short x = 1; month_table[x].name; ++x)
      ch->printf("\n\r\t[%02d] %s", x, month_table[x].name);

    ch->printf("\n\r\n\rSelect month you were born: ");
    connected = CON_GET_BIRTHMONTH;
    break;

  case CON_GET_BIRTHMONTH:
    ch->birthday[0] = atoi(argument.c_str());

    if(ch->birthday[0] < 1 || ch->birthday[0] > 12) {
      ch->printf("ERROR: Invalid month.\n\rWhat month were you born? ");
      break;
    }

    {
      ostrstream ost;
      ost << "Select day you were born [1 - " << month_table[ch->birthday[0]].length << "]: " << ends;
      ch->printf(ost.str());
    }

    connected = CON_GET_BIRTHDAY;
    break;

  case CON_GET_BIRTHDAY:
    ch->birthday[1] = atoi(argument.c_str());

    if(ch->birthday[1] < 1 || ch->birthday[1] > month_table[ch->birthday[0]].length) {
      ch->printf("ERROR: Invalid day.\n\rWhat day were you born? ");
      break;
    }

    ch->printf(ENDL + f_str_box("PK stands for player killing. A PK character can kill and be killed by other PK characters only. A NONPK character can't kill or be killed by any other player."));
    ch->printf(f_str_box("{!PK STATUS CAN'T BE CHANGED AFTER THIS POINT!{0", false));
    ch->printf("\n\r\t[1] PK\n\r\t[2] NONPK\n\r\n\r");
    ch->printf("Select PK status? ");
    connected = CON_GET_PK;
    break;
 
  case CON_GET_PK:
    if(argument != "1" && argument != "2") {
        ch->printf("PK: ERROR: Invalid selection.\n\rSelect PK status? ");
        break;
    }
 
    ch->PK -= (ch->PK = atoi(argument.c_str()));
    ch->printf(ENDL + f_str_box("{^CHARACTER CREATION COMPLETE!{0", true, false));
    ch->printf(f_str_box("Press [enter] to continue."));
    connected = CON_FINISH;
    break;
 
  case CON_FINISH:
    connected = CON_PLAYING;
    ch->next = ch_list;
    ch_list = ch;
    log_string(ch->name + "@" + host + " new player.");
    ch->printf(ENDL);
    ch->login = str_time().substr(0, 3) + str_time().substr(11, 5);
    ch->init_stats();
    ch->lvl = 1;
    ch->set_job(JOB_SQUIRE);
    ch->job[ch->cjob].lvl = 1;
    OBJ *obj;
    (obj = create_obj("Broad Sword"))->to(ch);
    ch->equip(obj, LOC_RHAND);
    (obj = create_obj("Leather Hat"))->to(ch);
    ch->equip(obj, LOC_HEAD);
    (obj = create_obj("Clothes"))->to(ch);
    ch->equip(obj, LOC_BODY);
    (obj = create_obj("Battle Boots"))->to(ch);
    ch->equip(obj, LOC_ACC);

    for(short x = 0; x < 4; ++x)
      create_obj("Potion")->to(ch);

    ch->restore();
    ch->to(get_area(AREA_ORBONNE_MONASTERY)->room[ROOM_OUTSIDE]);
    ch->print_MOTD();
    ch->save();
    wiz_echo("New player " + ch->name + "@" + ch->socket + " has logged in.\n\r");
    do_look(ch);
    ch->check_battle();
    break;
  }

  return;
}

bool DESC::playing(const string name) {
  for(DESC *d = desc_list; d; d = d->next) {
    if(d != this && d->ch && d->connected != CON_GET_NAME && d->connected != CON_GET_OLD_PASSWORD && capitalize(name) == capitalize(d->ch->name)) {
/*
      printf("That character is already playing. Connect anyway? [y/n] ");
      connected = CON_BREAK_CONNECT;
*/
      printf(f_str("That character is already playing from another location. Either log on and quit out from that location, or wait for your character to autoquit after being idle for 10 minutes."));
      close_socket();
      return true;
    }
  }
 
  return false;
}
 
bool DESC::multiplaying(const string name) {
//TEMP  if(ch->admin)
    return false;
 
  for(CH *ch = ch_list; ch; ch = ch->next) {
    if(!ch->npc && ch->name != name && !ch->admin && ch->socket == host && ch != this->ch) {
      printf("Multiplaying is not allowed.\n\r");
      close_socket();
      return true;
    }
  }

  for(DESC *d = desc_list; d; d = d->next) {
    if(d->ch->name != name && !d->ch->admin && d->host == host) {
      printf("Multiplaying is not allowed.\n\r");
      close_socket();
      return true;
    }
  }
 
  return false;
}

bool DESC::reconnect(const string name, bool fConn) {
  for(CH *ch2 = ch_list; ch2; ch2 = ch2->next) {
    if(!ch2->npc && (!fConn || !ch2->desc) && ch->name == ch2->name) {
      if(!fConn)
        ch->pwd = ch2->pwd;
      else {
        zap(ch);
        ch = ch2;
        ch->desc = this;
        ch->timer = 0;
        ch->printf("Reconnecting...\n\r");
        log_string(ch->name + "@" + host + " reconnected.");
        wiz_echo("wiznet> links> " + ch->name + " reconnected.\n\r", WIZ_LINKS);
        connected = CON_PLAYING;
      }

      return true;
    }
  }

  return false;
}

string DESC::conn_status() {
  return connected_table[connected].status;
}

bool DESC::write(const string str) {
  if(!(::write(desc, str.c_str(), str.length()))) {
    perror("DESC::write");
    return false;
  }

  return true;
}

bool write_to_desc(const short desc, const string str) {
  if(!(write(desc, str.c_str(), str.length()))) {
    perror("write_to_desc");
    return false;
  }
  
  return true;  
}