/* Copyright (c) 1993 by Sanjay Ghemawat */

#include <assert.h>
#include <ctype.h>
#include <string.h>
#include <pwd.h>
#include <unistd.h>

#include "Array.h"

#include "Year.h"
#include "Month.h"
#include "WeekDay.h"
#include "Date.h"

#include "arrays.h"
#include "basic.h"
#include "calendar.h"
#include "item.h"
#include "lexer.h"
#include "misc.h"
#include "options.h"
#include "uid.h"

/*
 * Info needed for old date set format.
 */
struct Item_OldDates {
    int		inited;
    int		isWeekly;
    SmallIntSet	days;
    SmallIntSet months;
    int		everyYear;
    int		firstYear;
    int		lastYear;

    DateList	deleteList;
};

const int Item::defaultRemindStart = 1;

static const char opener = '[';
static const char closer = ']';

Item::Item(Calendar* cal) {
    text = copy_string("");
    owner = copy_string("");

    uid = (char*) uid_new();
    uid_persistent = 0;

    calendar = cal;

    deleted = 0;
    remindStart = defaultRemindStart;

    dates = new DateSet;

    hilite = AlwaysHilite;
    options = 0;
}

Item::~Item() {
    delete [] text;
    delete [] owner;
    delete [] uid;
    delete dates;

    if (options != 0) delete options;
}

DateSet* Item::Dates() const {
    return dates;
}

int Item::Read(Lexer* lex) {
    Item_OldDates old;
    old.inited = 0;
    old.isWeekly = 0;
    old.days.Clear();
    old.months.Clear();
    old.everyYear = 1;

    while (1) {
	char c;
	char const* keyword;

	if (! lex->SkipWS() ||
	    ! lex->Peek(c)) {
	    lex->SetError("incomplete item");
	    return 0;
	}

	if (c == closer) {
	    /*
	     * Item is over.  Convert old date format into new format
	     * if necessary.
	     */

	    if (old.inited) {
		if (old.isWeekly) {
		    dates->set_week_set(old.days, old.months);
		}
		else {
		    dates->set_month_set(old.days, old.months);
		}
		if (! old.everyYear) {
		    dates->set_start(Date(1,Month::January(),old.firstYear));
		    dates->set_finish(Date(31,Month::December(),old.lastYear));
		}

		for (int i = 0; i < old.deleteList.size(); i++) {
		    dates->delete_occurrence(old.deleteList[i]);
		}
	    }

	    return 1;
	}

	if (! lex->GetId(keyword) ||
	    ! lex->SkipWS() ||
	    ! lex->Skip(opener)) {
	    lex->SetError("error reading item property name");
	    return 0;
	}

	if (! Parse(lex, keyword, old) ||
	    ! lex->SkipWS() ||
	    ! lex->Skip(closer)) {
	    lex->SetError("error reading item property");
	    return 0;
	}
    }
}

int Item::Parse(Lexer* lex, char const* keyword, Item_OldDates& old) {
    if (strcmp(keyword, "Remind") == 0) {
	if (! lex->SkipWS() ||
	    ! lex->GetNumber(remindStart)) {
	    lex->SetError("error reading remind level");
	    return 0;
	}
	return 1;
    }

    if (strcmp(keyword, "Owner") == 0) {
	char const* x;
	if (!lex->SkipWS() || !lex->GetId(x)) {
	    lex->SetError("error reading owner information");
	    return 0;
	}
	delete [] owner;
	owner = copy_string(x);
	return 1;
    }

    if (strcmp(keyword, "Uid") == 0) {
	char const* x;
	if (!lex->SkipWS() || !lex->GetUntil(closer, x)) {
	    lex->SetError("error reading unique id");
	    return 0;
	}
	delete [] uid;
	uid = copy_string(x);
	uid_persistent = 1;
	return 1;
    }

     if (strcmp(keyword, "Text") == 0) {
	/* Read text */
	int len;

	// Read length
	if (! lex->SkipWS() ||
	    ! lex->GetNumber(len) ||
	    ! (len >= 0) ||
	    ! lex->SkipWS() ||
	    ! lex->Skip(opener)) {
	    lex->SetError("error reading item text");
	    return 0;
	}

	// Allocate enough space for text
	delete [] text;
	text = new char[len+1];
	strcpy(text, "");

	if (! lex->GetText(text, len) ||
	    ! lex->Skip(closer)) {
	    delete [] text;
	    text = copy_string("");
	    lex->SetError("error reading item text");
	    return 0;
	}

	text[len] = '\0';
	return 1;
    }

    if (strcmp(keyword, "Dates") == 0) {
	if (! dates->read(lex)) {
	    lex->SetError("error reading date information");
	    return 0;
	}
	return 1;
    }

    if (strcmp(keyword, "Wdays") == 0) {
	old.inited = 1;
	old.isWeekly = 1;
	if (! old.days.Read(lex)) {
	    lex->SetError("error reading weekdays");
	    return 0;
	}
	return 1;
    }

    if (strcmp(keyword, "Mdays") == 0) {
	old.inited = 1;
	old.isWeekly = 0;
	if (! old.days.Read(lex)) {
	    lex->SetError("error reading monthdays");
	    return 0;
	}
	return 1;
    }

    if (strcmp(keyword, "Months") == 0) {
	old.inited = 1;
	if (! old.months.Read(lex)) {
	    lex->SetError("error reading set of months");
	    return 0;
	}
	return 1;
    }

    if (strcmp(keyword, "EveryYear") == 0) {
	old.inited = 1;
	old.everyYear = 1;
	return 1;
    }

    if (strcmp(keyword, "Years") == 0) {
	old.inited = 1;
	old.everyYear = 0;

	if (! lex->SkipWS() ||
	    ! lex->GetNumber(old.firstYear) ||
	    ! lex->SkipWS() ||
	    ! lex->GetNumber(old.lastYear)) {
	    lex->SetError("error reading range of years");
	    return 0;
	}
	return 1;
    }

    if (strcmp(keyword, "Deleted") == 0) {
	int day, month, year;

	if (! lex->SkipWS() ||
	    ! lex->GetNumber(day) ||
	    ! lex->SkipWS() ||
	    ! lex->GetNumber(month) ||
	    ! lex->SkipWS() ||
	    ! lex->GetNumber(year)) {
	    lex->SetError("error reading deletion date");
	    return 0;
	}

	old.inited = 1;
	old.deleteList.append(Date(day, Month::First()+(month-1), year));
	return 1;
    }

    if (strcmp(keyword, "Hilite") == 0) {
	char const* x;
	if (!lex->SkipWS() || !lex->GetId(x)) {
	    lex->SetError("error reading item hilite");
	    return 0;
	}

	hilite = parse_hilite(x);
	return 1;
    }

    char* key = copy_string(keyword);
    char const* val;
    if (! lex->GetString(val)) {
	lex->SetError("error reading item property");
	delete [] key;
	return 0;
    }

    if (options == 0) options = new OptionMap;
    options->store(key, val);
    delete [] key;
    return 1;
}

void Item::Write(FILE* out) const {
    fprintf(out, "Uid [%s]\n", uid);
    ((Item*) this)->uid_persistent = 1;

    if (strlen(owner) != 0) {
	fprintf(out, "Owner [%s]\n", owner);
    }

    fprintf(out, "Text [%d [%s]]\n", strlen(text), text);
    fprintf(out, "Remind [%d]\n", remindStart);
    fprintf(out, "Hilite [%s]\n", unparse_hilite(hilite));

    fprintf(out, "Dates [");
    dates->write(out);
    fprintf(out, "]\n");

    if (options != 0) {
	for (OptionMap_Bindings o = options->bindings(); o.ok(); o.next()) {
	    fprintf(out, "%s [", o.key());
	    Lexer::PutString(out, o.val());
	    fprintf(out, "]\n");
	}
    }
}

Calendar* Item::GetCalendar() const {
    return calendar;
}

void Item::SetCalendar(Calendar* cal) {
    calendar = cal;
}

void Item::CopyTo(Item* item) const {
    /*
     * I need to think more carefully about whether or not
     * unparsed data should be copied.  The safe solution is
     * to not copy it because all code should be able to deal
     * with the absence of unparsed data.
     *
     * Copying unparsed items on the other hand might be unsafe.
     * For example, unparsed data may contain unique ids
     * and it makes no sense to copy unique ids.
     *
     * So currently, unparsed data is not copied.
     */
    item->SetCalendar(GetCalendar());

    *item->dates = *dates;

    item->SetText(text);
    item->remindStart = remindStart;
    item->hilite = hilite;

    char* copy = copy_string(owner);
    delete [] item->owner;
    item->owner = copy;

    /* Do NOT copy uid.  That would defeat the whole purpose of uids */
}

const char* Item::GetText() const {
    return text;
}

void Item::SetText(const char* t) {
    char* copy = copy_string(t);
    delete [] text;
    text = copy;
}

int Item::GetRemindStart() const {
    return remindStart;
}

void Item::SetRemindStart(int r) {
    remindStart = r;
}

char const* Item::GetOwner() const {
    return owner;
}

static inline char const* my_id() {
    // effects - Return my name for storage in owner field
    //		 Returns 0 on error.

    static char const* my_name = 0;	// My user name
    static int got_name = 0;		// Has my_name been initialized?

    if (! got_name) {
	// Fetch my name from user database
	struct passwd* pw = getpwuid(getuid());
	if (pw != 0) my_name = copy_string(pw->pw_name);
	got_name = 1;
    }

    return my_name;
}

void Item::MakeOwner() {
    char const* id = my_id();
    if (id != 0) {
	delete [] owner;
	owner = copy_string(id);
    }
}

int Item::IsMine() const {
    char const* id = my_id();
    if ((id == 0) || (owner == 0)) {
	// Unknown owner
	return 0;
    }

    return (strcmp(id, owner) == 0);
}

char const* Item::GetUid() const {
    return uid;
}

int Item::IsUidPersistent() const {
    return uid_persistent;
}

int Item::ReadOnly() const {
    return GetCalendar()->ReadOnly();
}

Notice* Item::AsNotice() {
    return 0;
}

Appointment* Item::AsAppointment() {
    return 0;
}

int Item::Deleted() {
    return deleted;
}

void Item::MarkDeleted() {
    deleted = 1;
}

Item::HiliteMode Item::Hilite() const {
    return hilite;
}

void Item::Hilite(Item::HiliteMode m) {
    hilite = m;
}

Item::HiliteMode Item::parse_hilite(char const* x) {
    if (strcmp(x, "expire") == 0)	return ExpireHilite;
    if (strcmp(x, "never") == 0)	return NeverHilite;
    if (strcmp(x, "holiday") == 0)	return HolidayHilite;
    return AlwaysHilite;
}

char const* Item::unparse_hilite(Item::HiliteMode m) {
    if (m == ExpireHilite)	return "expire";
    if (m == NeverHilite)	return "never";
    if (m == HolidayHilite)	return "holiday";
    return "always";
}

Notice::Notice(Calendar* cal) : Item(cal) {
    length = 30;
}

Notice::~Notice() { }

int Notice::Parse(Lexer* lex, char const* keyword, Item_OldDates& old) {
    if (strcmp(keyword, "Length") == 0) {
	if (! lex->SkipWS() ||
	    ! lex->GetNumber(length)) {
	    lex->SetError("error reading notice display length");
	    return 0;
	}
	return 1;
    }

    return Item::Parse(lex, keyword, old);
}

void Notice::Write(FILE* out) const {
    fprintf(out, "Length [%d]\n", length);
    Item::Write(out);
}

Item* Notice::Clone() const {
    Notice* copy = new Notice(GetCalendar());

    Item::CopyTo(copy);
    copy->SetLength(length);
    return copy;
}

int Notice::GetLength() const {
    return length;
}

void Notice::SetLength(int l) {
    length = l;
}

Notice* Notice::AsNotice() {
    return this;
}

Appointment::Appointment(Calendar* cal) : Item(cal) {
    start = 30;
    length = 30;
    alarms = 0;
}

Appointment::~Appointment() {
    if (alarms != 0) {
	delete alarms;
    }
}

int Appointment::Parse(Lexer* lex, char const* keyword, Item_OldDates& old) {
    if (strcmp(keyword, "Start") == 0) {
	if (! lex->SkipWS() ||
	    ! lex->GetNumber(start)) {
	    lex->SetError("error reading appointment start time");
	    return 0;
	}
	return 1;
    }

    if (strcmp(keyword, "Length") == 0) {
	if (! lex->SkipWS() ||
	    ! lex->GetNumber(length)) {
	    lex->SetError("error reading appointment length");
	    return 0;
	}
	return 1;
    }

    if (strcmp(keyword, "Alarms") == 0) {
	if (alarms == 0) alarms = new intArray;
	alarms->clear();

	while (1) {
	    char c;

	    lex->SkipWS();
	    if (! lex->Peek(c)) {
		lex->SetError("error reading alarm list");
		return 0;
	    }

	    if (!isdigit(c)) break;

	    int num;
	    if (! lex->GetNumber(num)) return 0;
	    alarms->append(num);
	}

	return 1;
    }

    return Item::Parse(lex, keyword, old);
}

void Appointment::Write(FILE* out) const {
    fprintf(out, "Start [%d]\n", start);
    fprintf(out, "Length [%d]\n", length);
    if (alarms != 0) {
	fprintf(out, "Alarms [");
	for (int i = 0; i < alarms->size(); i++) {
	    int x = alarms->slot(i);
	    fprintf(out, " %d", x);
	}
	fprintf(out, "]\n");
    }

    Item::Write(out);
}

Item* Appointment::Clone() const {
    Appointment* copy = new Appointment(GetCalendar());

    Item::CopyTo(copy);
    copy->SetStart(start);
    copy->SetLength(length);

    if (copy->alarms != 0) {
	delete copy->alarms;
	copy->alarms = 0;
    }
    if (alarms != 0) {
	copy->alarms = new intArray(*alarms);
    }

    return copy;
}

int Appointment::GetStart() const {
    return start;
}

void Appointment::SetStart(int s) {
    start = s;
}

int Appointment::GetLength() const {
    return length;
}

void Appointment::SetLength(int l) {
    length = l;
}

int Appointment::GetFinish() const {
    return start + length;
}

intArray* Appointment::GetAlarms() const {
    return alarms;
}

void Appointment::SetAlarms(intArray* list) {
    if (alarms == 0) alarms = new intArray;
    alarms->clear();
    *alarms = *list;
}

Appointment* Appointment::AsAppointment() {
    return this;
}
