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


#include <string.h>
#include <stdio.h>

#include "calendar.h"
#include "item.h"
#include "lexer.h"
#include "version.h"

#include "arrays.h"

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

Calendar::Calendar()
     : items(*(new pointerArray)),
       includes(*(new pointerArray))
{
    readonly = 0;
}

Calendar::~Calendar() {
    int i;

    for (i = 0; i < items.size(); i++) {
	Item* item = (Item*) items[i];
	delete item;
    }
    delete &items;

    for (i = 0; i < includes.size(); i++) {
	char* includeName = (char*) includes[i];
	delete includeName;
    }
    delete &includes;
}

void Calendar::Add(Item* item) {
    item->SetCalendar(this);
    items.append((void*) item);
}

void Calendar::Remove(Item* item) {
    for (int i = 0; i < items.size(); i++) {
	if (items[i] == (void*) item) {
	    /* Found it */
	    delete item;

	    /* Shift the other items over */
	    for (int j = i + 1; j < items.size(); j++)
		items[j - 1] = items[j];

	    items.remove();
	    break;
	}
    }
}

int Calendar::Read(Lexer* lex) {
    for (int i = 0; i < items.size(); i++) {
	Item* item = (Item*) items[i];
	delete item;
    }
    items.clear();

    if (lex->Status() == Lexer::Error) {
	/* No input file at all */
	return 1;
    }

    int major, minor;
    const char* modifier;

    if (! lex->SkipWS() ||
	! lex->Skip("Calendar") ||
	! lex->SkipWS()) {
	lex->SetError("file does not contain calendar");
	return 0;
    }

    /*
     * Get file version number.
     * We can only understand version numbers that have a major
     * component <= to my major version number.
     */

    if (! lex->Skip(opener) ||
        ! lex->Skip('v') ||

	! lex->GetNumber(major) ||
	! (major >= 0) ||
	! (major <= VersionMajor) ||

	! lex->Skip('.') ||

        ! lex->GetNumber(minor) ||
	! (minor >= 0) ||

	! lex->GetUntil(closer, modifier) ||
	/* Possibly check modifier here */

	! lex->Skip(closer)) {
	lex->SetError("illegal version");
    }

    while (1) {
	char c;

	lex->SkipWS();
	lex->Peek(c);

	switch (lex->Status()) {
	  case Lexer::Eof:
	    return 1;
	  case Lexer::Error:
	    return 0;
	  default:
	    break;
	}

	char const* keyword;

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

	if (strcmp(keyword, "Appt") == 0) {
	    Item* item = new Appointment(this);
	    if (! item->Read(lex)) {
		delete item;
		return 0;
	    }
	    Add(item);
	}
	else if (strcmp(keyword, "Note") == 0) {
	    Item* item = new Notice(this);
	    if (! item->Read(lex)) {
		delete item;
		return 0;
	    }
	    Add(item);
	}
	else if (strcmp(keyword, "Include") == 0) {
	    /* Read the name */
	    int len;

	    if (! lex->SkipWS() ||
		! lex->GetNumber(len) ||
		! lex->SkipWS() ||
		! lex->Skip(opener)) {
		lex->SetError("error reading included file name");
		return 0;
	    }

	    char* name = new char[len + 1];
	    if (! lex->GetText(name, len) ||
		! lex->Skip(closer)) {
		delete name;
		lex->SetError("error reading included file name");
		return 0;
	    }
	    name[len] = '\0';
	    Include(name);
	    delete name;
	}
	else {
	    //lex->SetError("unrecognized keyword");
	    //return 0;
	    if (! extra.Read(keyword, lex)) {
		lex->SetError("error reading unknown keyword");
		return 0;
	    }
	}

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

void Calendar::Write(FILE* out) const {
    fprintf(out, "Calendar [v%d.%d]\n", VersionMajor, VersionMinor);

    for (int i = 0; i < includes.size(); i++) {
	char const* name = (char const*) includes[i];
	fprintf(out, "Include [%d [%s]]\n", strlen(name), name);
    }

    for (i = 0; i < items.size(); i++) {
	Item* item = (Item*) items[i];

	if (item->AsNotice() != 0) {
	    fprintf(out, "Note [\n");
	}
	else {
	    fprintf(out, "Appt [\n");
	}
	item->Write(out);
	fprintf(out, "]\n");
    }

    extra.Write(out);
}

int Calendar::Size() const {
    return items.size();
}

void Calendar::Include(char const* name) {
    char* name_copy = new char[strlen(name) + 1];
    strcpy(name_copy, name);

    includes.append((void*) name_copy);
}

void Calendar::Exclude(char const* name) {
    for (int i = 0; i < includes.size(); i++) {
	if (strcmp(name, ((char const*) includes[i])) == 0) {
	    char* includeName = (char*) includes[i];
	    delete includeName;

	    /* Shift other includes over */
	    for (int j = i + 1; j < includes.size(); j++) {
		includes[j - 1] = includes[j];
	    }
	    includes.remove();
	    return;
	}
    }
}

int Calendar::NumIncludes() const {
    return includes.size();
}

char const* Calendar::GetInclude(int i) const {
    return ((char const*) includes[i]);
}

Item* Calendar::Get(int i) const {
    return (Item*) items[i];
}
