# include <kernel/OS.h>
# include <app/Message.h>
# include <app/Messenger.h>
# include <app/Application.h>
# include <storage/FilePanel.h>
# include <storage/Path.h>
# include <interface/TextView.h>
# include <interface/Window.h>
# include <interface/MenuBar.h>
# include <interface/Menu.h>
# include <interface/MenuItem.h>
# include <interface/Button.h>
# include <interface/Alert.h>
# include <signal.h>


static int32 dgd_argc;		/* argument count */
static char **dgd_argv;		/* arguments */
static bool arg_started;	/* started with arguments? */
static bool dgd_running;	/* driver running? */
static bool dgd_fatal;		/* aborted with fatal error */
static BPath config_file;
static BPath restore_file;

extern "C" {
# include "dgd.h"
# include "version.h"

# undef bool
# undef exit

extern void conn_intr(void);

static thread_id driver;		/* driver thread */

/*
 * NAME:	term()
 * DESCRIPTION:	catch SIGTERM
 */
static void term(int arg)
{
    interrupt();
    conn_intr();
}

void dgd_exit(int code)
{
    dgd_running = FALSE;
    if (code != 0 && !arg_started) {
	exit_thread(code);
    } else {
	exit(code);
    }
}

void dgd_abort()
{
    dgd_running = FALSE;
    dgd_fatal = TRUE;
    exit_thread(2);
}

}


/*
 * NAME:	run_dgd()
 * DESCRIPTION:	actually run the driver
 */
static int32 run_dgd(void *data)
{
    char *argv[4];

    P_srandom((long) P_time());
    signal(SIGPIPE, SIG_IGN);
    signal(SIGTERM, term);

    if (!arg_started) {
	argv[0] = "DGD";
	argv[1] = (char *) config_file.Path();
	argv[2] = (char *) restore_file.Path();
	argv[3] = NULL;
	dgd_argv = argv;
	dgd_argc = (argv[2] == NULL) ? 2 : 3;
    }
    dgd_running = true;
    dgd_exit(dgd_main(dgd_argc, dgd_argv));
}

enum {
    DGD_CONFIG =	'dgdc',
    DGD_RESTORE =	'dgdr',
    DGD_START =		'dgds',
    DGD_MESSAGE =	'dgdm',

    DGD_CONFIG_DONE =	'dgdC',
    DGD_RESTORE_DONE =	'dgdR',

    DGD_LEFT =		47,
    DGD_TOP =		47,
    DGD_MENU =		19,
    DGD_WIDTH =		560,
    DGD_HEIGHT =	375,

    ABOUT_LEFT =	80,
    ABOUT_TOP =		80,
    ABOUT_WIDTH =	480,
    ABOUT_HEIGHT =	310,
    ABOUT_INDENT =	80,

    ABOUT_GRAY =	220
};


class LogView : public BTextView {
public:
    LogView();
    virtual void Select(int32 start, int32 finish);
    virtual void MakeFocus(bool flag = true);

    bool selected;
};

/*
 * NAME:	LogView->LogView()
 * DESCRIPTION:	LogView constructor
 */
LogView::LogView()
       : BTextView(BRect(2, DGD_MENU + 2, DGD_WIDTH + 2,
			 DGD_MENU + DGD_HEIGHT + 2),
		   NULL,
		   BRect(0, 0, DGD_WIDTH, DGD_HEIGHT),
		   be_fixed_font, NULL, 0, B_WILL_DRAW)
{
    MakeEditable(false);
    SetWordWrap(false);
    SetTabWidth(7);
    selected = false;
}

/*
 * NAME:	LogView->Select()
 * DESCRIPTION:	remember if anything was selected
 */
void LogView::Select(int32 start, int32 finish)
{
    selected = (start != finish);
    BTextView::Select(start, finish);
}

/*
 * NAME:	LogView->MakeFocus()
 * DESCRIPTION:	skip the BTextView MakeFocus stage
 */
void LogView::MakeFocus(bool flag)
{
    BView::MakeFocus(flag);
}


class MainFrame : public BWindow {
public:
    MainFrame();
    virtual void MessageReceived(BMessage *message);
    virtual bool QuitRequested(void);
    virtual void MenusBeginning(void);

private:
    BMenuItem *config;
    BMenuItem *restore;
    BMenuItem *start;
    BMenuItem *copy;
    BMenuItem *select;
    BMenuBar *menubar;
    LogView *log;
    short lines, length;
    short len[25];

    BFilePanel *config_panel;
    BFilePanel *restore_panel;
    entry_ref directory;
};


MainFrame::MainFrame()
	 : BWindow(BRect(DGD_LEFT, DGD_TOP, DGD_LEFT + DGD_WIDTH + 4,
			 DGD_TOP + DGD_MENU + DGD_HEIGHT + 4),
		   "DGD", B_TITLED_WINDOW, B_NOT_RESIZABLE | B_NOT_ZOOMABLE)
{
    BMenu *filemenu;
    BMenu *editmenu;
    BMenuItem *item;

    /* log window */
    log = new LogView();
    AddChild(log);
    log->MakeFocus();

    /* file menu */
    filemenu = new BMenu("File");
    config = new BMenuItem("Config File" B_UTF8_ELLIPSIS,
			   new BMessage(DGD_CONFIG), 'O');
    filemenu->AddItem(config);
    restore = new BMenuItem("Restore File" B_UTF8_ELLIPSIS,
			    new BMessage(DGD_RESTORE), 'R');
    filemenu->AddItem(restore);
    filemenu->AddItem(new BSeparatorItem());
    start = new BMenuItem("Start DGD", new BMessage(DGD_START), 'S');
    filemenu->AddItem(start);
    filemenu->SetTargetForItems(this);
    filemenu->AddItem(new BSeparatorItem());
    item = new BMenuItem("About DGD" B_UTF8_ELLIPSIS,
			 new BMessage(B_ABOUT_REQUESTED));
    item->SetTarget(be_app);
    filemenu->AddItem(item);
    filemenu->AddItem(new BSeparatorItem());
    item = new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED), 'Q');
    item->SetTarget(this);
    filemenu->AddItem(item);

    /* edit menu */
    editmenu = new BMenu("Edit");
    item = new BMenuItem("Undo", NULL, 'Z');
    item->SetEnabled(false);
    editmenu->AddItem(item);
    editmenu->AddItem(new BSeparatorItem());
    item = new BMenuItem("Cut", NULL, 'X');
    item->SetEnabled(false);
    editmenu->AddItem(item);
    copy = new BMenuItem("Copy", new BMessage(B_COPY), 'C');
    editmenu->AddItem(copy);
    item = new BMenuItem("Paste", NULL, 'V');
    item->SetEnabled(false);
    editmenu->AddItem(item);
    item = new BMenuItem("Clear", NULL);
    item->SetEnabled(false);
    editmenu->AddItem(item);
    editmenu->AddItem(new BSeparatorItem());
    select = new BMenuItem("Select All", new BMessage(B_SELECT_ALL), 'A');
    editmenu->AddItem(select);
    editmenu->SetTargetForItems(log);

    /* menu bar */
    menubar = new BMenuBar(BRect(0, 0, 0, 0), NULL);
    menubar->AddItem(filemenu);
    menubar->AddItem(editmenu);
    AddChild(menubar);

    lines = 0;
    length = 0;

    config_panel = NULL;
    restore_panel = NULL;
}

void MainFrame::MessageReceived(BMessage *message)
{
    entry_ref ref;

    switch (message->what) {
    case DGD_CONFIG:
	if (config_panel == NULL) {
	    config_panel = new BFilePanel(B_OPEN_PANEL, &BMessenger(this),
					  &directory, 0, false,
					  &BMessage(DGD_CONFIG_DONE));
	    config_panel->Window()->SetTitle("DGD: Config File");
	}
	config_panel->Show();
	break;

    case DGD_RESTORE:
	if (restore_panel == NULL) {
	    restore_panel = new BFilePanel(B_OPEN_PANEL, &BMessenger(this),
					   &directory, 0, false,
					   &BMessage(DGD_RESTORE_DONE));
	    restore_panel->Window()->SetTitle("DGD: Restore File");
	}
	restore_panel->Show();
	break;

    case DGD_START:
	driver = spawn_thread(run_dgd, "driver", B_NORMAL_PRIORITY, NULL);
	resume_thread(driver);
	break;

    case B_CANCEL:
	if (message->FindInt32("old_what") == DGD_CONFIG_DONE) {
	    config_panel->GetPanelDirectory(&directory);
	    delete config_panel;
	    config_panel = NULL;
	} else {
	    restore_panel->GetPanelDirectory(&directory);
	    delete restore_panel;
	    restore_panel = NULL;
	}
	break;

    case DGD_CONFIG_DONE:
	message->FindRef("refs", 0, &ref);
	BEntry(&ref).GetPath(&config_file);
	break;

    case DGD_RESTORE_DONE:
	message->FindRef("refs", 0, &ref);
	BEntry(&ref).GetPath(&restore_file);
	break;

    case DGD_MESSAGE:
	char *mesg, *p;
	int32 start, finish;
	bool eoln;
	short n;

	message->FindString("mesg", (const char **) &mesg);
	if (log->selected) {
	    log->GetSelection(&start, &finish);
	} else {
	    start = finish = 0;
	}

	if (lines == 0) {
	    /* first line */
	    select->SetEnabled(true);
	    lines = 1;
	    len[0] = 0;
	}

	eoln = false;
	for (;;) {
	    if (eoln || len[lines - 1] == 80) {
		if (!eoln) {
		    log->Insert(length, "\n", 1);
		    len[lines - 1]++;
		    length++;
		}
		if (lines == 25) {
		    /* delete first line */
		    log->Delete(0, len[0]);
		    length -= len[0];
		    start -= len[0];
		    finish -= len[0];
		    memcpy(len, len + 1, 24 * sizeof(short));
		} else {
		    /* add new line */
		    lines++;
		}
		len[lines - 1] = 0;
	    }
	    if (mesg[0] == '\0') {
		if (start < 0) {
		    start = 0;
		}
		if (finish < 0) {
		    finish = 0;
		}
		if (log->selected) {
		    log->Select(start, finish);
		}
		break;
	    }

	    p = strchr(mesg, LF);
	    if (p != NULL && len[lines - 1] + (n=++p - mesg) <= 81) {
		eoln = true;
	    } else {
		eoln = false;
		n = strlen(mesg);
		if (n + len[lines - 1] > 80) {
		    /* wrap line */
		    n = 80 - len[lines - 1];
		}
	    }

	    log->Insert(length, mesg, n);
	    mesg += n;
	    len[lines - 1] += n;
	    length += n;
	}
	break;

    default:
	BWindow::MessageReceived(message);
	break;
    }
}

bool MainFrame::QuitRequested(void)
{
    if (!dgd_running ||
	(new BAlert("",
		    "Are you sure you want to terminate the running process?",
		    "Yes", "No", NULL, B_WIDTH_AS_USUAL,
		    B_WARNING_ALERT))->Go() == 0) {
	be_app->PostMessage(&BMessage(B_QUIT_REQUESTED), NULL);
    }
    return false;
}

void MainFrame::MenusBeginning(void)
{
    config->SetEnabled(!dgd_running && !dgd_fatal);
    restore->SetEnabled(!dgd_running && !dgd_fatal);
    restore->SetMarked(restore_file.Path() != NULL);
    start->SetEnabled(!dgd_running && !dgd_fatal &&
		      config_file.Path() != NULL &&
		      config_panel == NULL && restore_panel == NULL);
    copy->SetEnabled(log->selected);
    select->SetEnabled(lines != 0);
}


static MainFrame *mainframe;

class DGD : public BApplication {
public:
    DGD();
    virtual void ArgvReceived(int32 argc, char **argv);
    virtual void RefsReceived(BMessage *message);
    virtual void AboutRequested(void);
    virtual bool QuitRequested(void);

/* private: icon */
};

DGD::DGD() : BApplication("application/x-DGD")
{
    mainframe = new MainFrame();
    mainframe->Show();
}

void DGD::ArgvReceived(int32 argc, char **argv)
{
    arg_started = true;
    driver = spawn_thread(run_dgd, "driver", B_NORMAL_PRIORITY, NULL);
    resume_thread(driver);
}

void DGD::RefsReceived(BMessage *message)
{
}

void DGD::AboutRequested(void)
{
    BWindow *about;
    BTextView *view;
    BFont font;
    uint32 dummy;
    char buf[100];
    BButton *button;

    about = new BWindow(BRect(ABOUT_LEFT, ABOUT_TOP, ABOUT_WIDTH + ABOUT_LEFT,
			      ABOUT_HEIGHT + ABOUT_TOP),
			NULL, B_MODAL_WINDOW, B_NOT_MOVABLE | B_NOT_RESIZABLE);
    view = new BTextView(BRect(0, 0, ABOUT_WIDTH, ABOUT_INDENT), NULL,
			 BRect(ABOUT_INDENT, 20, ABOUT_WIDTH - 10,
			       ABOUT_INDENT - 10),
			 0, B_WILL_DRAW);
    view->SetViewColor(ABOUT_GRAY, ABOUT_GRAY, ABOUT_GRAY);
    view->MakeEditable(false);
    view->MakeSelectable(false);
    view->GetFontAndColor(&font, &dummy);
    font.SetSize(font.Size() * 1.1);
    view->SetFontAndColor(&font);
    about->AddChild(view);
    sprintf(buf, "\
DGD %s\n© 1993 - 2001 BeeHive Internet Technologies, Inc. \
All Rights Reserved.", VERSION);
    view->SetText(buf);
    view = new BTextView(BRect(0, ABOUT_INDENT, ABOUT_WIDTH, ABOUT_HEIGHT),
			 NULL,
			 BRect(10, 0, ABOUT_WIDTH - 10,
			       ABOUT_HEIGHT - ABOUT_INDENT - 10),
			 0, B_WILL_DRAW);
    view->SetViewColor(ABOUT_GRAY, ABOUT_GRAY, ABOUT_GRAY);
    view->MakeEditable(false);
    view->MakeSelectable(false);
    about->AddChild(view);
    view->SetText("\
Permission is granted to copy the source and executables made therefrom, but \
any commercial distribution or use whatsoever is not allowed. Commercial \
distribution or use refers to any distribution or use from which any form of \
income is received regardless of profit therefrom, or from which any revenue \
or promotional value is received, as well as any distribution to or use in a \
corporate environment. Use of the source or executables made therefrom to \
promote or support a commercial venture is included in this restriction.\n\n\
Any modifications of this program lacking an explicit copyright notice are \
subject to the copyright notice stated herein. Any explicit copyright \
notices added to this program shall not contradict the terms stated herein, \
but may further restrict distribution or use. Any modifications should be \
made in such a way that it is clear that this copyright notice applies to \
the source, executables made therefrom and any derivatives thereof and in \
such a way that it is possible to regain the unmodified source.");
    button = new BButton(BRect(ABOUT_WIDTH / 2 - 30,
			 ABOUT_HEIGHT - ABOUT_INDENT - 40, ABOUT_WIDTH / 2 + 30,
			 ABOUT_HEIGHT - ABOUT_INDENT - 20),
			 NULL, "OK", new BMessage(B_QUIT_REQUESTED));
    view->AddChild(button);
    button->MakeDefault(true);
    button->SetTarget(about);
    about->Show();
}

bool DGD::QuitRequested()
{
    if (dgd_running) {
	interrupt();
	conn_intr();
	return false;
    }
    return true;
}


/*
 * NAME:	main()
 * DESCRIPTION:	main program
 */
int main(int argc, char *argv[])
{
    DGD *app;

    dgd_argc = argc;
    dgd_argv = argv;
    app = new DGD();
    app->Run();
    delete app;
    return 0;
}

/*
 * NAME:	P->message()
 * DESCRIPTION:	show message
 */
extern "C" void P_message(char *mesg)
{
    BMessage message(DGD_MESSAGE);

    message.AddString("mesg", mesg);
    mainframe->PostMessage(&message, NULL);
}
