/* ************************************************************************
*  file: save.c      Implementation of save files         Part of Copper3 *
*  Usage : load_char/save_char                                            *
*  Copyright (C) 1990, 1991 - see 'license.doc' for complete information. *
************************************************************************* */

#ident	"@(#) $Id:$\n"
/* $Log:$
 */

/*
 Crashproof saving of players and items, some parts and ideas
 originally by Taquin Ho (prometheus) and Jeff Stile (Abaddon),
 others from original db.c - Mostly rewritten for Copper III

 The call chain looks like this:

    Loading
    =======

    load_char(name):
	<read from file>
	store_to_char();
	load_obj(file);

    load_obj(file):
        store_to_obj();
        <recurse>


    Saving
    ======

    save_char(ch):
	char_to_store(ch,<store>);
	<write to file>
	save_obj(file);

    save_obj(file):
        obj_to_store(obj);
        <recurse>

*/



#include CONFIG

#if HAVE_STRINGS_H

#include <strings.h>
#endif

#if HAVE_STRING_H
#include <string.h>
#endif


#include <ctype.h>

#include "structs.h"
#include "save.h"
#include "utils.h"
#include "comm.h"
#include "player.h"
#include "db.h"
#include "error.h"
#include "proto.h"

#include <sys/timeb.h>

void obj_material_to_store(struct obj_material *om, struct obj_material_u *st);
void obj_affect_to_store(struct obj_affect *oa, struct obj_affect_u *st);
void org_to_store(struct char_org_data *org, struct org_u *st);
void skill_to_store(struct char_skill_data *sk, struct skill_u *st);
void phys_to_store(struct char_phys_data *ph, struct phys_u *st);
void affect_to_store(struct affected_type *af, struct affect_u *st);
void obj_to_store(struct obj_data *obj, struct obj_file_elem *object);
void char_to_store(struct char_data *ch, struct char_file_u *st);
void store_to_org(struct org_u *st, struct char_org_data *org);
void store_to_skill(struct skill_u *st, struct char_skill_data *sk);
void store_to_phys(struct phys_u *st, struct char_phys_data *ph);
void store_to_affect(struct affect_u *st, struct affected_type *af);
void store_to_obj_material(struct obj_material_u *st, struct obj_material *om);
void store_to_obj_affect(struct obj_affect_u *st, struct obj_affect *oa);
void store_to_obj(struct obj_data *obj, struct obj_file_elem *object);
void store_to_char(struct char_file_u *st, struct char_data *ch);
void save_obj(struct obj_data *obj,FILE *fl);
struct obj_data *load_obj(FILE *fl);
void save_string(char *str,FILE *fl);
char *load_string(FILE *fl);

int unlink(char *path);

extern struct obj_index_data *obj_index;
extern struct room_data *world;
extern int top_of_world;
extern char log_buf[];
extern size_t obj_info_size[];


/**** Loading Routines ****/

void load_world_obj()
{
    FILE *fl;
    struct obj_data *o;
    int to_room;

    if(!(fl=fopen("save/world.objs","r"))) {
        perror("fopen");
        log("BUG: Cannot open world object file");
        return;
    }

    while(!feof(fl)) {
        o=load_obj(fl);
        if(o) {
            to_room=o->in_room;
            o->in_room=NOWHERE;
            obj_to_room(o,to_room);
        }
    }
}

struct obj_data *load_obj(FILE *fl)
{
    int j;
    struct obj_data *obj,*contained;
    struct obj_file_elem object;
    struct obj_material_u material_elem;
    struct obj_material *material;
    struct obj_affect_u affect_elem;
    struct obj_affect *affect;
    struct obj_info type,*info;

    if(!fread(&object, sizeof(struct obj_file_elem), 1, fl))
        return NULL;

    obj = read_object(object.item_number, VIRTUAL);
    if(!obj) {
        log("BUG: Cannot load saved object");
        return NULL;
    }
    store_to_obj(obj,&object);

    for(j=0;j < object.num_materials;j++) {
	fread(&material_elem,sizeof(material_elem),1,fl);
	CREATE(material,struct obj_material,1);
        store_to_obj_material(&material_elem,material);
        material->next = obj->material;
        obj->material = material;
    }

    for(j=0;j < object.num_affects;j++) {
	fread(&affect_elem,sizeof(affect_elem),1,fl);
        CREATE(affect,struct obj_affect,1);
	store_to_obj_affect(&affect_elem,affect);
        affect->next = obj->affected;
        obj->affected = affect;
    }


    /* This could use some explanation:
     *
     * First, we read the obj_info type, so we know what kind
     * of record to expect.
     *
     * Then, we allocate that kind of info for the object, and
     * load into it starting after the obj_type entry the
     * remaining part of the record.
     */
    for(j=0;j< object.num_info;j++) {
        fread(&type,sizeof(type),1,fl);
        info=new_obj_info(type.obj_type,obj);

        fread(info+sizeof(struct obj_info),
              obj_info_size[type.obj_type]-sizeof(struct obj_info),
              1,fl);
    }

    /* Take care of contained items */
    for(j=0;j< object.num_contains;j++) {
	contained=load_obj(fl);
        if(contained)
            obj_to_obj(contained,obj);
    }
    return(obj);
}



/* Load a char, TRUE if loaded, FALSE if not */
struct char_data * load_char(char *name)
{
    FILE *fl;
    char buf[30];
    int result,i,eq_pos;
    struct char_file_u char_element;
    struct org_u org_elem;
    struct char_org_data *org;
    struct skill_u sk_elem;
    struct char_skill_data *sk;
    struct phys_u ph_elem;
    struct char_phys_data *ph, **phs;
    struct affect_u af_elem;
    struct affected_type *af;
    struct char_data *ch;
    struct obj_data *obj;

    CAP(name);

    sprintf(buf,"save/%s",name);
    if (!(fl = fopen(buf, "r")))
	return(NULL);

    ch = bare_char();

    result = fread(&char_element, sizeof(struct char_file_u), 1, fl);
    store_to_char(&char_element,ch);

    /*** Load all variable length items... ***/

    /* Affects */
    for(i=0;i<char_element.num_affect;i++) {
        result = fread(&af_elem,sizeof(struct affect_u),1,fl);
        CREATE(af,struct affected_type,1);
        store_to_affect(&af_elem,af);
        af->next=ch->affected;
        ch->affected=af;
    }


    /* Organizational information */
    ch->orgs=NULL;
    for(i=0;i<char_element.num_org;i++) {
        result = fread(&org_elem,sizeof(struct org_u),1,fl);
        CREATE(org,struct char_org_data,1);
        store_to_org(&org_elem,org);
        org->next=ch->orgs;
        ch->orgs=org;
    }

    /* Skills */
    for(i=0;i<char_element.num_skill;i++) {
        result = fread(&sk_elem,sizeof(struct skill_u),1,fl);
        CREATE(sk,struct char_skill_data,1);
        store_to_skill(&sk_elem,sk);
        sk->next=ch->skills;
        ch->skills=sk;
    }

    /* Physical */
    phs=&ch->physical;
    for(i=0;i<char_element.num_phys;i++) {
        result = fread(&ph_elem,sizeof(struct phys_u),1,fl);
        CREATE(ph,struct char_phys_data,1);
        store_to_phys(&ph_elem,ph);
        *phs = ph;
        ph->next=NULL;
        phs = &ph->next;
    }

    /* Player information */
    if(!(ch->specials.act & ACT_ISNPC)) {
        CREATE(ch->prefs,struct player_prefs,1);
        result = fread(ch->prefs,sizeof(struct player_prefs),1,fl);
    } else {
        ch->prefs = NULL;
    }

    ch->tmpabilities = ch->physical->abilities;

    /* Now load the objects */

    /* As yet, this does not properly equip the character */
    for(i=0;i < char_element.num_item;i++) {
        obj=load_obj(fl);
        if(obj) {
            if(obj->equipped_as != UNEQUIPPED) {
                /* Need to juggle this to stay sane */
                eq_pos = obj->equipped_as;
                obj->equipped_as = UNEQUIPPED;
                equip_char(ch,obj,eq_pos);
            } else {
        	    obj_to_char(obj,ch);
            }
        }
    }

    fclose(fl);

    return ch;
}






/****                 ****/
/**** Saving Routines ****/
/****                 ****/

int save_world_obj(int save_obj_state)
{
    static FILE *so_fl=NULL;
    struct obj_data *o;
    static int saving_room;
    int max;

    switch(save_obj_state) {
        case SO_START:
            if(!(so_fl=fopen("save/world.objs","w"))) {
                perror("fopen");
                log("BUG: Cannot save world objects");
                return -1;
            }
            saving_room=0;
            break;

        case SO_SAVE:
            max=saving_room+50;
            for(;saving_room<max && saving_room < top_of_world;saving_room++)
                for(o=world[saving_room].contents;o;o=o->next_content)
                    if(!(o->obj_flags.extra_flags & ITEM_IGNORE))
                        save_obj(o,so_fl);
            if(saving_room >= top_of_world)
                return 1;
            break;

        case SO_DONE:
            fclose(so_fl);
            break;

        case SO_SAVEALL:
            for(saving_room=0;saving_room < top_of_world;saving_room++)
                for(o=world[saving_room].contents;o;o=o->next_content)
                    if(!(o->obj_flags.extra_flags & ITEM_IGNORE))
                        save_obj(o,so_fl);
            break;
    }
    return 0;
}

/*
* Assumes file is open with write privilege
*/

void save_obj(struct obj_data *obj,FILE *fl)
{
    struct obj_data *tmp;
    struct obj_file_elem object;
    struct obj_material_u material_elem;
    struct obj_material *om;
    struct obj_affect_u affect_elem;
    struct obj_affect *oa;
    struct obj_info *info;

    obj_to_store(obj, &object);
    if (fwrite(&object, sizeof(struct obj_file_elem), 1, fl) < 1) {
	perror("writing save data -save_obj");
	exit(1);
    }

    for(om=obj->material;om;om=om->next) {
        obj_material_to_store(om,&material_elem);
        fwrite(&material_elem,sizeof(struct obj_material_u),1,fl);
    }

    for(oa=obj->affected;oa;oa=oa->next) {
        obj_affect_to_store(oa,&affect_elem);
        fwrite(&affect_elem,sizeof(struct obj_affect_u),1,fl);
    }

    for(info=obj->info;info;info=info->next) {
        /* This mumbo-jumbo is so we don't have *_u records for each
           info_type there is... and to save 4 bytes per record */

	/* NOTE: upon further consideration, things like the strings
	 * in obj_info_exit suggest we will have to use *_u records...
         * oh well...
         */
        if(fwrite(&info->obj_type,
               obj_info_size[info->obj_type] - sizeof(void *),
               1,fl) < 1) {
            perror("writing save data -save_obj infos");
            exit(1);
        }
    }

    for(tmp=obj->contains;tmp;tmp=tmp->next_content) {
	save_obj(tmp,fl);
    }
}


/* write the vital data of a player to the player file */
void save_char(struct char_data *ch)
{
    int result;
    struct char_file_u st;
    struct obj_data *obj;
    struct skill_u skill_elem;
    struct org_u org_elem;
    struct affect_u af_elem;
    struct phys_u phys_elem;
    struct char_skill_data *sk;
    struct char_phys_data *ph;
    struct char_org_data *org;
    struct affected_type *af;
    FILE *fl;
    char buf[30];

/*    if (IS_NPC(ch) || !ch->desc)
	return;
*/

    if(ch->specials.act & ACT_NOSAVE) {
	log("BUG: Trying to save a guest character");
	return;
    }

    char_to_store(ch, &st);

    if (IS_NPC(ch))
        sprintf(buf,"save/%d",ch->id);
    else
        sprintf(buf,"save/%s",GET_NAME(ch));
    if (!(fl = fopen(buf, "w")))
    {
	perror("save char");
	exit(1);
    }

    if(!(fwrite(&st, sizeof(struct char_file_u), 1, fl))) {
	perror("fwrite in save_char()");
	exit(1);
    }

    /*** Do variable length items here ***/

    /* Affects */
    for(af=ch->affected;af;af=af->next) {
        affect_to_store(af,&af_elem);
        result = fwrite(&af_elem,sizeof(struct affect_u),1,fl);
    }

    /* Organizational information */
    for(org=ch->orgs;org;org=org->next) {
        org_to_store(org,&org_elem);
        result = fwrite(&org_elem,sizeof(struct org_u),1,fl);
    }

    /* Skills */
    for(sk=ch->skills;sk;sk=sk->next) {
        skill_to_store(sk,&skill_elem);
        result = fwrite(&skill_elem,sizeof(struct skill_u),1,fl);
    }

    /* Physical */
    for(ph=ch->physical;ph;ph=ph->next) {
        phys_to_store(ph,&phys_elem);
        result = fwrite(&phys_elem,sizeof(struct phys_u),1,fl);
    }

    /* Player information */
    if(!(ch->specials.act & ACT_ISNPC)) {
        result = fwrite(ch->prefs,sizeof(struct player_prefs),1,fl);
    }

    /* Now save the objects */

    for(obj=ch->carrying;obj;obj=obj->next_content)
	save_obj(obj, fl);

    fclose(fl);
}


/*** Structure translations (store_to_* && *_store) ***/

void store_to_org(struct org_u *st, struct char_org_data *org)
{
    int i;

    org->org_id = st->org_id;
    org->member_level = st->member_level;
    org->permissions = st->permissions;
    org->participation = st->participation;
    org->experience = st->experience;
    for(i=0;i<MAX_DOMAINS;i++)
        org->domains[i] = st->domains[i];
}

void org_to_store(struct char_org_data *org, struct org_u *st)
{
    int i;

    st->org_id = org->org_id;
    st->member_level = org->member_level;
    st->permissions = org->permissions;
    st->participation = org->participation;
    st->experience = org->experience;
    for(i=0;i<MAX_DOMAINS;i++)
        st->domains[i] = org->domains[i];
}

void store_to_skill(struct skill_u *st, struct char_skill_data *sk)
{
    sk->skill_num = st->skill_num;
    sk->learned = st->learned;
    sk->period = st->period;
    sk->period_type = st->period_type;
    sk->last = st->last;
}

void store_to_phys(struct phys_u *st, struct char_phys_data *ph)
{
    memcpy(ph,st,sizeof(struct phys_u));
}

void skill_to_store(struct char_skill_data *sk, struct skill_u *st)
{
    st->skill_num = sk->skill_num;
    st->learned = sk->learned;
    st->period = sk->period;
    st->period_type = sk->period_type;
    st->last = sk->last;
}

void phys_to_store(struct char_phys_data *ph, struct phys_u *st)
{
    memcpy(st,ph,sizeof(struct phys_u));
}

void store_to_affect(struct affect_u *st, struct affected_type *af)
{
    af->type = st->type;
    af->duration = st->duration;
    af->modifier = st->modifier;
    af->location = st->location;
    af->bitvector = st->bitvector;
}

void store_to_obj_material(struct obj_material_u *st, struct obj_material *om)
{
    om->type = st->type;
    om->subtype = st->subtype;
    om->purity = st->purity;
    om->weight = st->weight;
}

void obj_material_to_store(struct obj_material *om, struct obj_material_u *st)
{
    st->type = om->type;
    st->subtype = om->subtype;
    st->purity = om->purity;
    st->weight = om->weight;
}

void store_to_obj_affect(struct obj_affect_u *st, struct obj_affect *oa)
{
    oa->location = st->location;
    oa->modifier = st->modifier;
}

void obj_affect_to_store(struct obj_affect *oa, struct obj_affect_u *st)
{
    st->location = oa->location;
    st->modifier = oa->modifier;
}

void affect_to_store(struct affected_type *af, struct affect_u *st)
{
    st->type = af->type;
    st->duration = af->duration;
    st->modifier = af->modifier;
    st->location = af->location;
    st->bitvector = af->bitvector;
}

void store_to_obj(struct obj_data *obj, struct obj_file_elem *object)
{
    obj->obj_flags.extra_flags = object->extra_flags;
    obj->obj_flags.weight      = object->weight;
    obj->obj_flags.bitvector   = object->bitvector;

    if(object->load_code==LOAD_TO_EQ)
        obj->equipped_as=object->load_to;
    else if(object->load_code==LOAD_TO_ROOM)
        obj->in_room=object->load_to;
}

void obj_to_store(struct obj_data *obj, struct obj_file_elem *object)
{
    struct obj_data *j;
    struct obj_affect *oa;
    struct obj_material *om;
    struct obj_info *oi;
    int i;

    object->item_number = obj_index[obj->item_number].virtual;
    object->extra_flags = obj->obj_flags.extra_flags;
    object->weight  = obj->obj_flags.weight;
    object->bitvector  = obj->obj_flags.bitvector;

    if(obj->equipped_as!=UNEQUIPPED) {
        object->load_code = LOAD_TO_EQ;
        object->load_to = obj->equipped_as;
    } else if(obj->in_room != NOWHERE) {
        object->load_code = LOAD_TO_ROOM;
        object->load_to = obj->in_room;
    } else if(obj->carried_by) {
        object->load_code = LOAD_TO_INV;
    } else if(obj->in_obj) {
        object->load_code = LOAD_TO_OBJ;
    }

    i=0;
    for(j=obj->contains;j;j=j->next_content)
        i++;
    object->num_contains = i;

    i=0;
    for(om=obj->material;om;om=om->next)
        i++;
    object->num_materials = i;

    i=0;
    for(oa=obj->affected;oa;oa=oa->next)
        i++;
    object->num_affects = i;

    i=0;
    for(oi=obj->info;oi;oi=oi->next)
        i++;
    object->num_info = i;
}


/* copy data from the file structure to a char struct */	
void store_to_char(struct char_file_u *st, struct char_data *ch)
{
    int i;

    ch->id = st->id;
    GET_SEX(ch) = st->sex;
    /*GET_CLASS(ch) = st->class;
    GET_LEVEL(ch) = st->level;*/

    ch->player.short_descr = 0;
    ch->player.long_descr = 0;

    if (*st->title)
    {
	CREATE(ch->player.title, char, strlen(st->title) + 1);
	strcpy(ch->player.title, st->title);
    }
    else
	GET_TITLE(ch) = 0;

    if (*st->description)
    {
	CREATE(ch->player.description, char, 
	    strlen(st->description) + 1);
	strcpy(ch->player.description, st->description);
    }
    else
	ch->player.description = 0;

    ch->player.hometown = st->hometown;

    ch->player.time.birth = st->birth;
    ch->player.time.played = st->played;
    ch->player.time.logon  = time(0);

    for (i = 0; i <= MAX_TOUNGE - 1; i++)
	ch->player.talks[i] = st->talks[i];

    GET_WEIGHT(ch) = st->weight;
    GET_HEIGHT(ch) = st->height;

    ch->points = st->points;
    if (ch->physical->max_mana < 100) {
        ch->physical->max_mana = 100;
    } /* if */

    ch->specials.alignment    = st->alignment;
    ch->specials.position     = st->position;

    ch->specials.act          = st->act;
    ch->specials.carry_weight = 0;
    ch->specials.carry_items  = 0;
    ch->physical->armor          = 100;
    ch->points.hitroll        = 0;
    ch->points.damroll        = 0;

    CREATE(GET_NAME(ch), char, strlen(st->name) +1);
    strcpy(GET_NAME(ch), st->name);

    /* Not used as far as I can see (Michael) */
    for(i = 0; i <= 4; i++)
     ch->specials.apply_saving_throw[i] = st->apply_saving_throw[i];

    ch->in_room = real_room(st->load_room);
    affect_total(ch);
} /* store_to_char */

/* copy vital data from a players char-structure to the file structure */
void char_to_store(struct char_data *ch, struct char_file_u *st)
{
    int i;
    struct affected_type *af;
    struct char_skill_data *sk;
    struct char_org_data *org;
    struct char_phys_data *ph;
    struct obj_data *obj;

    /* Unaffect everything a character can be affected by */
/*
    for(j=ch->carrying;j;j=j->next_content) {
	/ * save in case we erase in unequip_char * /
	i = ch->equipment->equipped_as;
	temp = unequip_char(ch, ch->equipment);
	temp->equipped_as = i;
	temp->next_content = char_eq;
	char_eq = temp;
    }*/

    ch->tmpabilities = ch->physical->abilities;

    st->birth      = ch->player.time.birth;
    st->played     = ch->player.time.played;
    st->played    += (long) (time(0) - ch->player.time.logon);
    st->last_logon = time(0);

    st->load_room = world[ch->in_room].number;

    st->id = ch->id;
    st->hometown = ch->player.hometown;
    st->weight   = GET_WEIGHT(ch);
    st->height   = GET_HEIGHT(ch);
    st->sex      = GET_SEX(ch);
    st->points    = ch->points;
    st->alignment       = ch->specials.alignment;
    st->position        = ch->specials.position;
    st->act             = ch->specials.act;

    st->points.hitroll =  0;
    st->points.damroll =  0;

    if (GET_TITLE(ch))
	strcpy(st->title, GET_TITLE(ch));
    else
	*st->title = '\0';

    if (ch->player.description)
	strcpy(st->description, ch->player.description);
    else
	*st->description = '\0';


    for (i = 0; i < MAX_TOUNGE; i++)
	st->talks[i] = ch->player.talks[i];

    strcpy(st->name, GET_NAME(ch) );

    for(i = 0; i <= 4; i++)
     st->apply_saving_throw[i] = ch->specials.apply_saving_throw[i];

    for(af = ch->affected,i = 0; af; af=af->next) {
        i++;
    }
    st->num_affect = i;

    for(sk = ch->skills,i = 0; sk; sk=sk->next) {
        i++;
    }
    st->num_skill = i;

    for(org = ch->orgs,i = 0; org; org=org->next) {
        i++;
    }
    st->num_org = i;

    for(ph = ch->physical,i = 0; ph; ph=ph->next) {
        i++;
    }
    st->num_phys = i;

    for(obj = ch->carrying,i = 0; obj; obj=obj->next_content) {
        i++;
    }
    st->num_item = i;

    affect_total(ch);
} /* Char to store */

/* String routines */

void save_string(char *str,FILE *fl)
{
	size_t size;

	if(str)
		size = strlen(str);
	else
		size = 0;

	if(fwrite(&size,sizeof(size),1,fl)!=1) {
		log("BUG: Can't write in save_string");
		return;
	}

	if(!size)
		return;

	if(fwrite(str,sizeof(char),size,fl)!=size) {
		log("BUG: Can't write in save_string");
		return;
	}
}

char *load_string(FILE *fl)
{
	char buf[MAX_STRING_LENGTH];
	size_t size;

	if(fread(&size,sizeof(size),1,fl)!=1) {
		log("BUG: Can't read in load_string");
		return NULL;
	}

	if(!size)
		return NULL;

	if(fread(buf,sizeof(char),size,fl)!=1) {
		log("BUG: Can't read in load_string");
		return NULL;
	}

	buf[size]='\0';

	return (char *)strdup(buf);
}

/**** In cases where we don't care anymore... ****/

void delete_char(struct char_data *ch)
{
    char buf[100];
    int ret;

    sprintf(buf,"save/%s",GET_NAME(ch));
    ret = unlink(buf);
}