/* Copyright (c) 1993 by Sanjay Ghemawat */
#include <assert.h>
#include <string.h>

#include "misc_tcl.h"
#include "cal_tcl.h"
#include "item_tcl.h"
#include "dispatch.h"

extern "C" {
    struct Tcl_HashEntry;
    struct Tcl_HashTable;
#include <tclHash.h>
}

/*
 * Item* -> Item_Tcl* map.
 */
static Tcl_HashTable itemMap;
static int itemMapInited = 0;

Item_Tcl::Item_Tcl(Tcl_Interp* tcl, Item* i) : Object(tcl, "Item") {
    item = i;

    if (! itemMapInited) {
	itemMapInited = 1;
	Tcl_InitHashTable(&itemMap, TCL_ONE_WORD_KEYS);
    }
    int newentry;
    Tcl_HashEntry* entry = Tcl_CreateHashEntry(&itemMap,(char*)item,&newentry);
    assert(newentry);
    Tcl_SetHashValue(entry, (ClientData)this);
}

Item_Tcl::~Item_Tcl() {
    Tcl_HashEntry* entry = Tcl_FindHashEntry(&itemMap, (char*)item);
    assert((entry != 0) && (Tcl_GetHashValue(entry) == ((ClientData) this)));
    Tcl_DeleteHashEntry(entry);
}

Item_Tcl* Item_Tcl::find(Item* item) {
    Tcl_HashEntry* entry = Tcl_FindHashEntry(&itemMap, (char*)item);
    return ((entry == 0) ? 0 : ((Item_Tcl*) Tcl_GetHashValue(entry)));
}

/*
 * Notice Handler
 *
 *	<n> length			-- Returns integer
 *	<n> length <length>
 *	<n> clone			-- Returns new <item>
 *	<n> is <type>			-- Returns boolean
 */

/*
 * Appointment Handler
 *
 *	<a> length			-- Returns integer
 *	<a> length <length>
 *	<a> starttime			-- Returns minutes
 *	<a> starttime <minutes>
 *	<a> clone			-- Returns new <item>
 *	<a> is appt			-- Returns boolean
 */


/*
 * Item Handler
 *
 *	<i> delete
 *
 *	<i> calendar			-- Returns <calendar>
 *	<i> text			-- Returns text
 *	<i> text <text>
 *	<i> earlywarning		-- Returns integer
 *	<i> earlywarning <days>
 *
 *	<i> contains <date>		-- Returns boolean
 *	<i> empty			-- Returns boolean
 *	<i> repeats			-- Returns boolean
 *	<i> first			-- Returns date
 *	<i> next <date>			-- Returns date/signals error
 *	<i> type			-- Returns repetition type (string)
 *
 *	<i> date <date>
 *	<i> dayrepeat <interval> <anchor>
 *	<i> monthrepeat <interval> <anchor>
 *	<i> weekdays <weekday>...
 *	<i> start <date>
 *	<i> finish <date>
 *	<i> deleteon <date>
 *
 *	?<i> unparse			-- Returns <string>
 *	?<i> parse <string>
 */

/*
 * Forward declaration of handler procedures.
 */
static int item_length	(ClientData, Tcl_Interp*, int, char*[]);
static int item_startt	(ClientData, Tcl_Interp*, int, char*[]);
static int item_clone	(ClientData, Tcl_Interp*, int, char*[]);
static int item_is	(ClientData, Tcl_Interp*, int, char*[]);
static int item_delete	(ClientData, Tcl_Interp*, int, char*[]);
static int item_cal	(ClientData, Tcl_Interp*, int, char*[]);
static int item_text	(ClientData, Tcl_Interp*, int, char*[]);
static int item_early	(ClientData, Tcl_Interp*, int, char*[]);
static int item_empty	(ClientData, Tcl_Interp*, int, char*[]);
static int item_repeat	(ClientData, Tcl_Interp*, int, char*[]);
static int item_first	(ClientData, Tcl_Interp*, int, char*[]);
static int item_type	(ClientData, Tcl_Interp*, int, char*[]);
static int item_cont	(ClientData, Tcl_Interp*, int, char*[]);
static int item_next	(ClientData, Tcl_Interp*, int, char*[]);
static int item_date	(ClientData, Tcl_Interp*, int, char*[]);
static int item_start	(ClientData, Tcl_Interp*, int, char*[]);
static int item_finish	(ClientData, Tcl_Interp*, int, char*[]);
static int item_ondel	(ClientData, Tcl_Interp*, int, char*[]);
static int item_dayr	(ClientData, Tcl_Interp*, int, char*[]);
static int item_monthr	(ClientData, Tcl_Interp*, int, char*[]);
static int item_wdays	(ClientData, Tcl_Interp*, int, char*[]);

static Dispatch_Entry item_dispatch[] = {
    { "length",		0, 1, item_length	},
    { "starttime",	0, 1, item_startt	},
    { "clone",		0, 0, item_clone	},
    { "is",		1, 1, item_is		},
    { "delete",		0, 0, item_delete	},
    { "calendar",	0, 0, item_cal		},
    { "text",		0, 1, item_text		},
    { "earlywarning",	0, 1, item_early	},
    { "empty",		0, 0, item_empty	},
    { "repeats",	0, 0, item_repeat	},
    { "first",		0, 0, item_first	},
    { "type",		0, 0, item_type		},
    { "contains",	1, 1, item_cont		},
    { "next",		1, 1, item_next		},
    { "date",		1, 1, item_date		},
    { "start",		1, 1, item_start	},
    { "finish",		1, 1, item_finish	},
    { "deleteon",	1, 1, item_ondel	},
    { "dayrepeat",	2, 2, item_dayr		},
    { "monthrepeat",	2, 2, item_monthr	},
    { "weekdays",	1, -1, item_wdays	},
    { 0,		0, 0, 0			}
};

int Item_Tcl::method(int argc, char* argv[]) {
    return Dispatch(item_dispatch, (ClientData)this, tcl(), argc, argv);
}

/*
 * Handler procedures.
 */

static int item_length(ClientData c, Tcl_Interp* tcl, int argc, char* argv[]) {
    Item_Tcl* item = (Item_Tcl*) c;

    Notice* note = item->value()->AsNotice();
    if (note != 0) {
	if (argc == 0) {
	    char buffer[100];
	    sprintf(buffer, "%d", note->GetLength());
	    Tcl_SetResult(tcl, buffer, TCL_VOLATILE);
	    return TCL_OK;
	}

	int length;
	if ((Tcl_GetInt(tcl, argv[0], &length) != TCL_OK) || (length < 1)){
	    TCL_Error(tcl, "invalid notice length");
	}
	note->SetLength(length);
	TCL_Return(tcl, "");
    }

    Appointment* appt = item->value()->AsAppointment();
    if (appt != 0) {
	if (argc == 0) {
	    char buffer[100];
	    sprintf(buffer, "%d", appt->GetLength());
	    Tcl_SetResult(tcl, buffer, TCL_VOLATILE);
	    return TCL_OK;
	}

	int length;
	if ((Tcl_GetInt(tcl, argv[0], &length) != TCL_OK) ||
	    (length < 1) ||
	    (length > 24*60)) {
	    TCL_Error(tcl, "invalid appointment length");
	}
	appt->SetLength(length);
	TCL_Return(tcl, "");
    }

    TCL_Error(tcl, "unknown command");
}

static int item_startt(ClientData c, Tcl_Interp* tcl, int argc, char* argv[]) {
    Item_Tcl* item = (Item_Tcl*) c;

    Appointment* appt = item->value()->AsAppointment();
    if (appt == 0) {
	TCL_Error(tcl, "unknown command");
    }

    if (argc == 0) {
	char buffer[100];
	sprintf(buffer, "%d", appt->GetStart());
	Tcl_SetResult(tcl, buffer, TCL_VOLATILE);
	return TCL_OK;
    }

    int start;
    if ((Tcl_GetInt(tcl, argv[0], &start) != TCL_OK) ||
	(start < 0) ||
	(start >= (24*60))) {
	TCL_Error(tcl, "invalid appointment start");
    }
    appt->SetStart(start);
    TCL_Return(tcl, "");
}

static int item_clone(ClientData c, Tcl_Interp* tcl, int argc, char* argv[]) {
    Item_Tcl* item = (Item_Tcl*) c;
    Item_Tcl* clone = new Item_Tcl(tcl, item->value()->Clone());
    TCL_Return(tcl, (char*) clone->handle());
}

static int item_is(ClientData c, Tcl_Interp* tcl, int argc, char* argv[]) {
    Item_Tcl* item = (Item_Tcl*) c;

    if ((strcmp(argv[0], "note") == 0) && (item->value()->AsNotice() != 0)) {
	TCL_Return(tcl, "1");
    }

    if ((strcmp(argv[0], "appt") == 0) && (item->value()->AsAppointment()!=0)){
	TCL_Return(tcl, "1");
    }

    TCL_Return(tcl, "0");
}

static int item_delete(ClientData c, Tcl_Interp* tcl, int argc, char* argv[]) {
    Item_Tcl* item = (Item_Tcl*) c;
    delete item;
    TCL_Return(tcl, "");
}

static int item_cal(ClientData c, Tcl_Interp* tcl, int argc, char* argv[]) {
    Item_Tcl* item = (Item_Tcl*) c;

    char const* cal = calendar_instance->container(item);
    if (cal == 0) {
	TCL_Error(tcl, "item not in calendar");
    }
    TCL_Return(tcl, (char*)cal);
}

static int item_text(ClientData c, Tcl_Interp* tcl, int argc, char* argv[]) {
    Item_Tcl* item = (Item_Tcl*) c;

    if (argc == 0) {
	TCL_Return(tcl, (char*) item->value()->GetText());
    }

    item->value()->SetText(argv[0]);
    TCL_Return(tcl, "");
}

static int item_early(ClientData c, Tcl_Interp* tcl, int argc, char* argv[]) {
    Item_Tcl* item = (Item_Tcl*) c;

    if (argc == 0) {
	char buffer[100];

	sprintf(buffer, "%d", item->value()->GetRemindStart());
	Tcl_SetResult(tcl, buffer, TCL_VOLATILE);
	return TCL_OK;
    }

    int warn;
    if (Tcl_GetInt(tcl, argv[0], &warn) != TCL_OK) {
	TCL_Error(tcl, "invalid early warning option");
    }
    item->value()->SetRemindStart(warn);
    TCL_Return(tcl, "");
}

static int item_empty(ClientData c, Tcl_Interp* tcl, int argc, char* argv[]) {
    Item_Tcl* item = (Item_Tcl*) c;
    TCL_Return(tcl, (item->value()->Dates()->empty()?"1":"0"));
}

static int item_repeat(ClientData c, Tcl_Interp* tcl, int argc, char* argv[]) {
    Item_Tcl* item = (Item_Tcl*) c;
    TCL_Return(tcl,(item->value()->Dates()->repeats()?"1":"0"));
}

static int item_first(ClientData c, Tcl_Interp* tcl, int argc, char* argv[]) {
    Item_Tcl* item = (Item_Tcl*) c;
    Date d;
    if (! item->value()->Dates()->first(d)) {
	TCL_Error(tcl, "item does not occur");
    }

    char buffer[100];
    sprintf(buffer, "%d", d.EpochDays());
    Tcl_SetResult(tcl, buffer, TCL_VOLATILE);
    return TCL_OK;
}

static int item_type(ClientData c, Tcl_Interp* tcl, int argc, char* argv[]) {
    Item_Tcl* item = (Item_Tcl*) c;
    char* result;
    switch (item->value()->Dates()->type()) {
      case DateSet::None:
	result = "";
	break;
      case DateSet::Daily:
	result = "Daily";
	break;
      case DateSet::Weekly:
	result = "Weekly";
	break;
      case DateSet::BiWeekly:
	result = "Every Two Weeks";
	break;
      case DateSet::ThreeWeekly:
	result = "Every Three Weeks";
	break;
      case DateSet::FourWeekly:
	result = "Every Four Weeks";
	break;
      case DateSet::Monthly:
	result = "Monthly";
	break;
      case DateSet::TwoMonthly:
	result = "Every Two Months";
	break;
      case DateSet::ThreeMonthly:
	result = "Every Three Months";
	break;
      case DateSet::FourMonthly:
	result = "Every Four Months";
	break;
      case DateSet::SixMonthly:
	result = "Every Six Months";
	break;
      case DateSet::Annual:
	result = "Annual";
	break;
      case DateSet::Other:
	result = "Complex";
	break;
    }
    TCL_Return(tcl, result);
}

static int item_cont(ClientData c, Tcl_Interp* tcl, int argc, char* argv[]) {
    Item_Tcl* item = (Item_Tcl*) c;
    int dateDays;
    if (Tcl_GetInt(tcl, argv[0], &dateDays) != TCL_OK) {
	TCL_Error(tcl, "invalid date");
    }
    Date date(dateDays);
    TCL_Return(tcl, (item->value()->Dates()->contains(date)?"1":"0"));
}

static int item_next(ClientData c, Tcl_Interp* tcl, int argc, char* argv[]) {
    Item_Tcl* item = (Item_Tcl*) c;
    int dateDays;
    if (Tcl_GetInt(tcl, argv[0], &dateDays) != TCL_OK) {
	TCL_Error(tcl, "invalid date");
    }
    Date date(dateDays);
    Date next;
    if (! item->value()->Dates()->next(date, next)) {
	TCL_Error(tcl, "no next occurrence for item");
    }

    char buffer[100];
    sprintf(buffer, "%d", next.EpochDays());
    Tcl_SetResult(tcl, buffer, TCL_VOLATILE);
    return TCL_OK;
}

static int item_date(ClientData c, Tcl_Interp* tcl, int argc, char* argv[]) {
    Item_Tcl* item = (Item_Tcl*) c;
    int dateDays;
    if (Tcl_GetInt(tcl, argv[0], &dateDays) != TCL_OK) {
	TCL_Error(tcl, "invalid date");
    }
    Date date(dateDays);
    item->value()->Dates()->set_date(date);
    TCL_Return(tcl, "");
}

static int item_start(ClientData c, Tcl_Interp* tcl, int argc, char* argv[]) {
    Item_Tcl* item = (Item_Tcl*) c;
    int dateDays;
    if (Tcl_GetInt(tcl, argv[0], &dateDays) != TCL_OK) {
	TCL_Error(tcl, "invalid date");
    }
    Date date(dateDays);
    item->value()->Dates()->set_start(date);
    TCL_Return(tcl, "");
}

static int item_finish(ClientData c, Tcl_Interp* tcl, int argc, char* argv[]) {
    Item_Tcl* item = (Item_Tcl*) c;
    int dateDays;
    if (Tcl_GetInt(tcl, argv[0], &dateDays) != TCL_OK) {
	TCL_Error(tcl, "invalid date");
    }
    Date date(dateDays);
    item->value()->Dates()->set_finish(date);
    TCL_Return(tcl, "");
}

static int item_ondel(ClientData c, Tcl_Interp* tcl, int argc, char* argv[]) {
    Item_Tcl* item = (Item_Tcl*) c;
    int dateDays;
    if (Tcl_GetInt(tcl, argv[0], &dateDays) != TCL_OK) {
	TCL_Error(tcl, "invalid date");
    }
    Date date(dateDays);
    item->value()->Dates()->delete_occurrence(date);
    TCL_Return(tcl, "");
}

static int item_dayr(ClientData c, Tcl_Interp* tcl, int argc, char* argv[]) {
    Item_Tcl* item = (Item_Tcl*) c;

    int interval;
    if ((Tcl_GetInt(tcl, argv[0], &interval) != TCL_OK) || (interval < 1)) {
	TCL_Error(tcl, "invalid interval");
    }

    int anchorDays;
    if (Tcl_GetInt(tcl, argv[1], &anchorDays) != TCL_OK) {
	TCL_Error(tcl, "invalid anchor date");
    }
    Date anchor(anchorDays);

    item->value()->Dates()->set_day_based_repeat(interval, anchor);
    TCL_Return(tcl, "");
}

static int item_monthr(ClientData c, Tcl_Interp* tcl, int argc, char* argv[]) {
    Item_Tcl* item = (Item_Tcl*) c;

    int interval;
    if ((Tcl_GetInt(tcl, argv[0], &interval) != TCL_OK) || (interval < 1)) {
	TCL_Error(tcl, "invalid interval");
    }

    int anchorDays;
    if (Tcl_GetInt(tcl, argv[1], &anchorDays) != TCL_OK) {
	TCL_Error(tcl, "invalid anchor date");
    }
    Date anchor(anchorDays);

    item->value()->Dates()->set_month_based_repeat(interval, anchor);
    TCL_Return(tcl, "");
}

static int item_wdays(ClientData c, Tcl_Interp* tcl, int argc, char* argv[]) {
    Item_Tcl* item = (Item_Tcl*) c;

    /* Collect weekdays */
    SmallIntSet set;
    set.Clear();
    for (int i = 0; i < argc; i++) {
	int weekday;
	if ((Tcl_GetInt(tcl, argv[i], &weekday) != TCL_OK) ||
	    (weekday < 1) ||
	    (weekday > 7)) {
	    TCL_Error(tcl, "invalid weekday");
	}
	set.Insert(weekday);
    }

    /* Repeat every month */
    SmallIntSet months;
    months.Clear();
    for (i = 1; i <= 12; i++) {
	months.Insert(i);
    }

    item->value()->Dates()->set_week_set(set, months);
    TCL_Return(tcl, "");
}
