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

#include <assert.h>
#include <string.h>
#include "Array.h"

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

#include "basic.h"
#include "calendar.h"
#include "item.h"
#include "lexer.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::textSize = 2048;
const int Item::defaultRemindStart = 1;

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

Item::Item(Calendar* cal) {
    text = new char[textSize];
    strcpy(text, "");

    calendar = cal;

    deleted = 0;
    remindStart = defaultRemindStart;

    dates = new DateSet;
}

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

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, "Text") == 0) {
	/* Read text */
	int len;

	if (! lex->SkipWS() ||
	    ! lex->GetNumber(len) ||
	    ! (len >= 0) ||
	    ! (len < textSize) ||
	    ! lex->SkipWS() ||
	    ! lex->Skip(opener) ||
	    ! lex->GetText(text, len) ||
	    ! lex->Skip(closer)) {
	    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;
    }

    /* Store away unknown item property */
    return (extra.Read(keyword, lex));
}

void Item::Write(FILE* out) const {
    fprintf(out, "Text [%d [%s]]\n", strlen(text), text);
    fprintf(out, "Remind [%d]\n", remindStart);
    fprintf(out, "Dates [");
    dates->write(out);
    fprintf(out, "]\n");

    extra.Write(out);
}

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;
}

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

void Item::SetText(const char* t) {
    int len = strlen(t);
    if (len >= textSize) len = textSize - 1;
    strncpy(text, t, len);
    text[len] = '\0';
}

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

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

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;
}

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;
    }
    else {
	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;
}

Appointment::~Appointment() { }

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;
    }
    else if (strcmp(keyword, "Length") == 0) {
	if (! lex->SkipWS() ||
	    ! lex->GetNumber(length)) {
	    lex->SetError("error reading appointment length");
	    return 0;
	}
	return 1;
    }
    else {
	return Item::Parse(lex, keyword, old);
    }
}

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

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

    Item::CopyTo(copy);
    copy->SetStart(start);
    copy->SetLength(length);
    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;
}

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

implementList(ItemList,Item*)
