/***************************************************************************
 *  Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,        *
 *  Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.   *
 *                                                                         *
 *  Merc Diku Mud improvments copyright (C) 1992, 1993 by Michael          *
 *  Chastain, Michael Quan, and Mitchell Tse.                              *
 *                                                                         *
 *  In order to use any part of this Merc Diku Mud, you must comply with   *
 *  both the original Diku license in 'license.doc' as well the Merc       *
 *  license in 'license.txt'.  In particular, you may not remove either of *
 *  these copyright notices.                                               *
 *                                                                         *
 *  Much time and thought has gone into this software and you are          *
 *  benefitting.  We hope that you share your changes too.  What goes      *
 *  around, comes around.                                                  *
 ***************************************************************************/

/*
 MurkMUD++ - A Windows compatible, C++ compatible Merc 2.2 Mud.

 \author Jon A. Lambert
 \date 01/02/2007
 \version 1.5
 \remarks
  This source code copyright (C) 2005, 2006, 2007 by Jon A. Lambert
  All rights reserved.

  Use governed by the MurkMUD++ public license found in license.murk++
*/

#include "os.hpp"
#include "config.hpp"
#include "globals.hpp"
#include "io.hpp"
#include "utils.hpp"
#include "database.hpp"

#include "shop.hpp"
#include "note.hpp"
#include "affect.hpp"
#include "exit.hpp"
#include "reset.hpp"
#include "area.hpp"
#include "room.hpp"
#include "objproto.hpp"
#include "object.hpp"
#include "mobproto.hpp"
#include "extra.hpp"
#include "world.hpp"

Database* Database::_instance = 0;

Database* Database::instance() {
  if (_instance == 0)
    _instance = new Database;
  return _instance;
}

Database::Database() {
  database = NULL;
  fBootDb = false;
}

Database::~Database() {
}

bool Database::initialize(std::string name)
{
  if (database != NULL)
    return true;
  if(sqlite3_open(name.c_str(), &database)) {
    fatal_printf("Can't open database: %s.", sqlite3_errmsg(database));
    return false;
  }
  return true;
}

void Database::shutdown(void) {
  if (database != NULL) {
    sqlite3_close(database);
    database = NULL;
  }
}

void Database::boot(void) {

  fBootDb = true;

  /*
   * Seed random number generator.
   */
  OS_SRAND ((unsigned int)std::time (NULL));

  // Set welcome screen
  load_greeting();

  /*
   * Read in all the area files.
   */
  std::ifstream fpList;
  std::ifstream fp;

  fpList.open (AREA_LIST, std::ifstream::in | std::ifstream::binary);
  if (!fpList.is_open()) {
    fatal_printf (AREA_LIST);
  }

  for (;;) {
    strArea = fread_word (fpList);
    if (strArea[0] == '$')
      break;

    fp.open (strArea.c_str(), std::ifstream::in | std::ifstream::binary);
    if (!fp.is_open()) {
      fatal_printf (strArea.c_str());
    }
    fpArea = &fp;
    for (;;) {
      std::string word;

      if (fread_letter (fp) != '#') {
        fatal_printf ("Boot_db: # not found.");
      }

      word = fread_word (fp);

      if (word[0] == '$')
        break;
      else if (!str_cmp (word, "AREA"))
        load_area (fp);
      else if (!str_cmp (word, "MOBILES"))
        load_mobiles (fp);
      else if (!str_cmp (word, "MOBPROGS"))
        load_mobprogs (fp);
      else if (!str_cmp (word, "OBJECTS"))
        load_objects (fp);
      else if (!str_cmp (word, "RESETS"))
        load_resets (fp);
      else if (!str_cmp (word, "ROOMS"))
        load_rooms (fp);
      else if (!str_cmp (word, "SHOPS"))
        load_shops (fp);
      else if (!str_cmp (word, "SPECIALS"))
        load_specials (fp);
      else {
        fatal_printf ("Boot_db: bad section name.");
      }
    }

    fp.close();
    fpArea = NULL;
  }
  fpList.close();

  /*
   * Fix up exits.
   * Declare db booting over.
   * Reset all areas once.
   * Load up the notes file.
   * Set the MOBtrigger.
   */
  fix_exits ();
  fBootDb = false;
  load_notes ();
  MOBtrigger = true;

  return;
}

void Database::load_area (std::ifstream & fp)
{
  Area *pArea = new Area();
  pArea->name = fread_string (fp);
  g_world->add_area(pArea);
  area_last = pArea;
  return;
}

void Database::load_greeting(void)
{
  sqlite3_stmt *stmt = NULL;
  Database* db = Database::instance();

  char *sql = sqlite3_mprintf("SELECT text FROM helps WHERE keyword = 'GREETING'");
  if (sqlite3_prepare(db->database, sql, -1, &stmt, 0) != SQLITE_OK) {
    bug_printf("Could not prepare statement: '%s' Error: %s", sql, sqlite3_errmsg(db->database));
    sqlite3_free(sql);
    return;
  }

  if (sqlite3_step(stmt) == SQLITE_ROW) {
    help_greeting.assign((const char*)sqlite3_column_text( stmt, 0 ));
  }

  sqlite3_finalize(stmt);
  sqlite3_free(sql);
  return;
}

void Database::load_mobiles (std::ifstream & fp)
{
  MobPrototype *pMobIndex;

  for (;;) {
    sh_int vnum;
    char letter;

    letter = fread_letter (fp);
    if (letter != '#') {
      fatal_printf ("Load_mobiles: # not found.");
    }

    vnum = fread_number (fp);
    if (vnum == 0)
      break;

    fBootDb = false;
    if (get_mob_index (vnum) != NULL) {
      fatal_printf ("Load_mobiles: vnum %d duplicated.", vnum);
    }
    fBootDb = true;

    pMobIndex = new MobPrototype();
    pMobIndex->vnum = vnum;
    pMobIndex->name = fread_string (fp);
    pMobIndex->short_descr = fread_string (fp);
    pMobIndex->long_descr = fread_string (fp);
    pMobIndex->description = fread_string (fp);

    pMobIndex->long_descr[0] = toupper (pMobIndex->long_descr[0]);
    pMobIndex->description[0] = toupper (pMobIndex->description[0]);

    pMobIndex->actflags = fread_number (fp) | ACT_IS_NPC;
    pMobIndex->affected_by = fread_number (fp);
    pMobIndex->pShop = NULL;
    pMobIndex->alignment = fread_number (fp);
    letter = fread_letter (fp);
    pMobIndex->level = number_fuzzy (fread_number (fp));
    pMobIndex->sex = fread_number (fp);

    if (letter != 'S') {
      fatal_printf ("Load_mobiles: vnum %d non-S.", vnum);
    }

    letter = fread_letter (fp);
    if (letter == '>') {
      fp.unget();
      mprog_read_programs (fp, pMobIndex);
    } else
      fp.unget();
    mob_table.insert (std::map<int, MobPrototype*>::value_type (vnum, pMobIndex));
    kill_table[URANGE (0, pMobIndex->level, MAX_LEVEL - 1)].number++;
  }

  return;
}

void Database::load_objects (std::ifstream & fp)
{
  ObjectPrototype *pObjIndex;

  for (;;) {
    sh_int vnum;
    char letter;

    letter = fread_letter (fp);
    if (letter != '#') {
      fatal_printf ("Load_objects: # not found.");
    }

    vnum = fread_number (fp);
    if (vnum == 0)
      break;

    fBootDb = false;
    if (get_obj_index (vnum) != NULL) {
      fatal_printf ("Load_objects: vnum %d duplicated.", vnum);
    }
    fBootDb = true;

    pObjIndex = new ObjectPrototype();
    pObjIndex->vnum = vnum;
    pObjIndex->name = fread_string (fp);
    pObjIndex->short_descr = fread_string (fp);
    pObjIndex->description = fread_string (fp);
    /* Action description */ fread_string (fp);

    pObjIndex->short_descr[0] = tolower (pObjIndex->short_descr[0]);
    pObjIndex->description[0] = toupper (pObjIndex->description[0]);

    pObjIndex->item_type = fread_number (fp);
    pObjIndex->extra_flags = fread_number (fp);
    pObjIndex->wear_flags = fread_number (fp);
    pObjIndex->value[0] = fread_number (fp);

    // Translate spells into internal "skill numbers."
    switch (pObjIndex->item_type) {
    case ITEM_PILL:
    case ITEM_POTION:
    case ITEM_SCROLL:
      pObjIndex->value[1] = skill_lookup (fread_word (fp));
      pObjIndex->value[2] = skill_lookup (fread_word (fp));
      pObjIndex->value[3] = skill_lookup (fread_word (fp));
      break;

    case ITEM_STAFF:
    case ITEM_WAND:
      pObjIndex->value[1] = fread_number (fp);
      pObjIndex->value[2] = fread_number (fp);
      pObjIndex->value[3] = skill_lookup (fread_word (fp));
      break;
    default:
      pObjIndex->value[1] = fread_number (fp);
      pObjIndex->value[2] = fread_number (fp);
      pObjIndex->value[3] = fread_number (fp);
    }


    pObjIndex->weight = fread_number (fp);
    pObjIndex->cost = fread_number (fp);        /* Unused */
    /* Cost per day */ fread_number (fp);

    if (pObjIndex->item_type == ITEM_POTION)
      SET_BIT (pObjIndex->extra_flags, ITEM_NODROP);

    for (;;) {
      char letter;

      letter = fread_letter (fp);

      if (letter == 'A') {
        Affect* paf = new Affect();
        paf->type = -1;
        paf->duration = -1;
        paf->location = fread_number (fp);
        paf->modifier = fread_number (fp);
        paf->bitvector = 0;
        pObjIndex->affected.push_back(paf);
      } else if (letter == 'E') {
        ExtraDescription* ed = new ExtraDescription();
        ed->keyword = fread_string (fp);
        ed->description = fread_string (fp);
        pObjIndex->extra_descr.push_back(ed);
      } else {
        fp.unget();
        break;
      }
    }

    obj_table.insert (std::map<int, ObjectPrototype*>::value_type (vnum, pObjIndex));
  }

  return;
}

void Database::load_resets (std::ifstream & fp)
{
  Reset *pReset;

  if (area_last == NULL) {
    fatal_printf ("Load_resets: no #AREA seen yet.");
  }

  for (;;) {
    Room *pRoomIndex;
    Exit *pexit;
    char letter;

    if ((letter = fread_letter (fp)) == 'S')
      break;

    if (letter == '*') {
      fread_to_eol (fp);
      continue;
    }

    pReset = new Reset();
    pReset->command = letter;
    /* if_flag */ fread_number (fp);
    pReset->arg1 = fread_number (fp);
    pReset->arg2 = fread_number (fp);
    pReset->arg3 = (letter == 'G' || letter == 'R')
      ? 0 : fread_number (fp);
    fread_to_eol (fp);

    /*
     * Validate parameters.
     * We're calling the index functions for the side effect.
     */
    switch (letter) {
    default:
      fatal_printf ("Load_resets: bad command '%c'.", letter);
      break;

    case 'M':
      get_mob_index (pReset->arg1);
      get_room_index (pReset->arg3);
      break;

    case 'O':
      get_obj_index (pReset->arg1);
      get_room_index (pReset->arg3);
      break;

    case 'P':
      get_obj_index (pReset->arg1);
      get_obj_index (pReset->arg3);
      break;

    case 'G':
    case 'E':
      get_obj_index (pReset->arg1);
      break;

    case 'D':
      pRoomIndex = get_room_index (pReset->arg1);

      if (pReset->arg2 < 0
        || pReset->arg2 > 5
        || (pexit = pRoomIndex->exit[pReset->arg2]) == NULL
        || !IS_SET (pexit->exit_info, EX_ISDOOR)) {
        fatal_printf ("Load_resets: 'D': exit %d not door.", pReset->arg2);
      }

      if (pReset->arg3 < 0 || pReset->arg3 > 2) {
        fatal_printf ("Load_resets: 'D': bad 'locks': %d.", pReset->arg3);
      }

      break;

    case 'R':
//      pRoomIndex = get_room_index (pReset->arg1);
      get_room_index (pReset->arg1);

      if (pReset->arg2 < 0 || pReset->arg2 > 6) {
        fatal_printf ("Load_resets: 'R': bad exit %d.", pReset->arg2);
      }

      break;
    }

    area_last->reset_list.push_back(pReset);
  }

  return;
}

void Database::load_rooms (std::ifstream & fp)
{
  Room *pRoomIndex;

  if (area_last == NULL) {
    fatal_printf ("Load_resets: no #AREA seen yet.");
  }

  for (;;) {
    sh_int vnum;
    char letter;
    int door;

    letter = fread_letter (fp);
    if (letter != '#') {
      fatal_printf ("Load_rooms: # not found.");
    }

    vnum = fread_number (fp);
    if (vnum == 0)
      break;

    fBootDb = false;
    if (get_room_index (vnum) != NULL) {
      fatal_printf ("Load_rooms: vnum %d duplicated.", vnum);
    }
    fBootDb = true;

    pRoomIndex = new Room();
    pRoomIndex->area = area_last;
    pRoomIndex->vnum = vnum;
    pRoomIndex->name = fread_string (fp);
    pRoomIndex->description = fread_string (fp);
    /* Area number */ fread_number (fp);
    pRoomIndex->room_flags = fread_number (fp);
    pRoomIndex->sector_type = fread_number (fp);
    pRoomIndex->light = 0;
    for (door = 0; door <= 5; door++)
      pRoomIndex->exit[door] = NULL;

    for (;;) {
      letter = fread_letter (fp);

      if (letter == 'S')
        break;

      if (letter == 'D') {
        Exit *pexit;
        int locks;

        door = fread_number (fp);
        if (door < 0 || door > 5) {
          fatal_printf ("Fread_rooms: vnum %d has bad door number.", vnum);
        }

        pexit = new Exit();
        pexit->description = fread_string (fp);
        pexit->name = fread_string (fp);
        pexit->exit_info = 0;
        locks = fread_number (fp);
        pexit->key = fread_number (fp);
        pexit->vnum = fread_number (fp);

        switch (locks) {
        case 1:
          pexit->exit_info = EX_ISDOOR;
          break;
        case 2:
          pexit->exit_info = EX_ISDOOR | EX_PICKPROOF;
          break;
        }

        pRoomIndex->exit[door] = pexit;
      } else if (letter == 'E') {
        ExtraDescription *ed;

        ed = new ExtraDescription();
        ed->keyword = fread_string (fp);
        ed->description = fread_string (fp);
        pRoomIndex->extra_descr.push_back(ed);
      } else {
        fatal_printf ("Load_rooms: vnum %d has flag not 'DES'.", vnum);
      }
    }

    room_table.insert (std::map<int, Room*>::value_type (vnum, pRoomIndex));
  }

  return;
}

void Database::load_shops (std::ifstream & fp)
{
  Shop *pShop;

  for (;;) {
    MobPrototype *pMobIndex;
    int iTrade;

    pShop = new Shop();
    pShop->keeper = fread_number (fp);
    if (pShop->keeper == 0)
      break;
    for (iTrade = 0; iTrade < MAX_TRADE; iTrade++)
      pShop->buy_type[iTrade] = fread_number (fp);
    pShop->profit_buy = fread_number (fp);
    pShop->profit_sell = fread_number (fp);
    pShop->open_hour = fread_number (fp);
    pShop->close_hour = fread_number (fp);
    fread_to_eol (fp);
    pMobIndex = get_mob_index (pShop->keeper);
    pMobIndex->pShop = pShop;

    shop_list.push_back(pShop);
  }

  return;
}

void Database::load_specials (std::ifstream & fp)
{
  for (;;) {
    MobPrototype *pMobIndex;
    char letter;

    switch (letter = fread_letter (fp)) {
    default:
      fatal_printf ("Load_specials: letter '%c' not *MS.", letter);

    case 'S':
      return;

    case '*':
      break;

    case 'M':
      pMobIndex = get_mob_index (fread_number (fp));
      pMobIndex->spec_fun = spec_lookup (fread_word (fp));
      if (pMobIndex->spec_fun == 0) {
        fatal_printf ("Load_specials: 'M': vnum %d.", pMobIndex->vnum);
      }
      break;
    }

    fread_to_eol (fp);
  }
}

void Database::load_notes (void)
{
  std::ifstream fp;

  fp.open (NOTE_FILE, std::ifstream::in | std::ifstream::binary);
  if (!fp.is_open())
    return;

  for (;;) {
    Note *pnote;
    char letter;

    do {
      letter = fp.get();
      if (fp.eof()) {
        fp.close();
        return;
      }
    } while (isspace (letter));
    fp.unget();

    pnote = new Note();

    if (str_cmp (fread_word (fp), "sender"))
      break;
    pnote->sender = fread_string (fp);

    if (str_cmp (fread_word (fp), "date"))
      break;
    pnote->date = fread_string (fp);

    if (str_cmp (fread_word (fp), "stamp"))
      break;
    pnote->date_stamp = fread_number (fp);

    if (str_cmp (fread_word (fp), "to"))
      break;
    pnote->to_list = fread_string (fp);

    if (str_cmp (fread_word (fp), "subject"))
      break;
    pnote->subject = fread_string (fp);

    if (str_cmp (fread_word (fp), "text"))
      break;
    pnote->text = fread_string (fp);

    note_list.push_back(pnote);
  }

  strArea = NOTE_FILE;
  fpArea = &fp;
  fatal_printf ("Load_notes: bad key word.");
  return;
}

/*
 * Translate all room exits from virtual to real.
 * Has to be done after all rooms are read in.
 * Check for bad reverse exits.
 */
void Database::fix_exits (void)
{
  Room *to_room;
  Exit *pexit;
  Exit *pexit_rev;
  int door;

  std::map<int,Room*>::iterator proom;
  for (proom = room_table.begin(); proom != room_table.end(); proom++) {
    bool fexit;

    fexit = false;
    for (door = 0; door <= 5; door++) {
      if ((pexit = (*proom).second->exit[door]) != NULL) {
        fexit = true;
        if (pexit->vnum <= 0)
          pexit->to_room = NULL;
        else
          pexit->to_room = get_room_index (pexit->vnum);
      }
    }

    if (!fexit)
      SET_BIT ((*proom).second->room_flags, ROOM_NO_MOB);
  }

  for (proom = room_table.begin(); proom != room_table.end(); proom++) {
    for (door = 0; door <= 5; door++) {
      if ((pexit = (*proom).second->exit[door]) != NULL
        && (to_room = pexit->to_room) != NULL
        && (pexit_rev = to_room->exit[rev_dir[door]]) != NULL
        && pexit_rev->to_room != (*proom).second) {
        bug_printf ("Fix_exits: %d:%d -> %d:%d -> %d.",
          (*proom).second->vnum, door,
          to_room->vnum, rev_dir[door], (pexit_rev->to_room == NULL)
          ? 0 : pexit_rev->to_room->vnum);
      }
    }
  }

  return;
}