/*
 * Copyright (c) 1987, 1988, 1989, 1990, 1991 Stanford University
 * Copyright (c) 1991 Silicon Graphics, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and 
 * its documentation for any purpose is hereby granted without fee, provided
 * that (i) the above copyright notices and this permission notice appear in
 * all copies of the software and related documentation, and (ii) the names of
 * Stanford and Silicon Graphics may not be used in any advertising or
 * publicity relating to the software without the specific, prior written
 * permission of Stanford and Silicon Graphics.
 * 
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY 
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  
 *
 * IN NO EVENT SHALL STANFORD OR SILICON GRAPHICS BE LIABLE FOR
 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF 
 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 
 * OF THIS SOFTWARE.
 */

/*
 * Session - coordinate control flow and display management
 */

#include <Dispatch/dispatcher.h>
#include <Dispatch/iohandler.h>
#include <InterViews/cursor.h>
#include <InterViews/display.h>
#include <InterViews/event.h>
#include <InterViews/session.h>
#include <InterViews/style.h>
#include <InterViews/window.h>
#include <OS/host.h>
#include <OS/file.h>
#include <OS/list.h>
#include <OS/string.h>
#include <ctype.h>
#include <cstdio>
#include <stdlib.h>

#ifdef sgi
#include <malloc.h>
#endif

/*
 * These attributes must be defined somewhere.
 */

static PropertyData defpropvalues[] = {
    { "*background", "#ffffff" },
    { "*foreground", "#000000" },
    { "*font", "fixed" },
    { nil }
};

/*
 * Predefined command-line options.
 */

static OptionDesc defoptions[] = {
    { "-background", "*background", OptionValueNext },
    { "-bg", "*background", OptionValueNext },
    { "-dbuf", "*double_buffered", OptionValueImplicit, "on" },
    { "-display", "*display", OptionValueNext },
    { "-dpi", "*dpi", OptionValueNext },
    { "-fg", "*foreground", OptionValueNext },
    { "-flat", "*flat", OptionValueNext },
    { "-fn", "*font", OptionValueNext },
    { "-font", "*font", OptionValueNext },
    { "-foreground", "*foreground", OptionValueNext },
    { "-geometry", "*geometry", OptionValueNext },
    { "-iconic", "*iconic", OptionValueImplicit, "on" },
    { "-monochrome", "*gui", OptionValueImplicit, "monochrome" },
    { "-motif", "*gui", OptionValueImplicit, "Motif" },
    { "-name", "*name", OptionValueNext },
    { "-nodbuf", "*double_buffered", OptionValueImplicit, "off" },
    { "-noshape", "*shaped_windows", OptionValueImplicit, "off" },
    { "-openlook", "*gui", OptionValueImplicit, "OpenLook" },
    { "-reverse", "*reverseVideo", OptionValueImplicit, "on" },
    { "-rv", "*reverseVideo", OptionValueImplicit, "on" },
    { "-shape", "*shaped_windows", OptionValueImplicit, "on" },
    { "-smotif", "*gui", OptionValueImplicit, "SGIMotif" },
    { "-synchronous", "*synchronous", OptionValueImplicit, "on" },
    { "+synchronous", "*synchronous", OptionValueImplicit, "off" },
    { "-title", "*title", OptionValueNext },
    { "-visual", "*visual", OptionValueNext },
    { "-visual_id", "*visual_id", OptionValueNext },
    { "-xrm", nil, OptionPropertyNext },
#ifdef sgi
    { "-malloc", "*malloc_debug", OptionValueImplicit, "on" },
#endif
    { nil }
};

/*
 * Session representation.
 */

declarePtrList(DisplayList,Display)
implementPtrList(DisplayList,Display)

class SessionRep {
private:
    friend class Session;

    SessionRep();
    ~SessionRep();
public:
    void handle_display_input(Display*);
private:
    int argc_;
    char** argv_;
    boolean done_;
    boolean readinput_;
    const char* classname_;
    String* name_;
    Style* style_;
    const PropertyData* props_;
    Display* default_;
    DisplayList* displays_;
    static Session* instance_;

    void init(
	const char*, int& argc, char** argv,
	const OptionDesc*, const PropertyData*, Display*
    );
    void parse_args(int& argc, char** argv, const OptionDesc*);
    boolean match(
	const String& arg, const OptionDesc& o, int& i, int argc, char** argv
    );
    void extract(
	const String& arg, const OptionDesc& o, int& i, int argc, char** argv,
	String& name, String& value
    );
    void bad_arg(const char* fmt, const String& arg);
    String next_arg(
	int& i, int argc, char** argv, const char* message, const String&
    );
    boolean find_arg(const String& name, String& value);

    void init_style(const char*, const PropertyData*);
    String* find_name();
    void load_props(Style*, const PropertyData*, int priority);
    void load_app_defaults(Style*, int priority);
    void load_environment(Style*, int priority);
    void load_path(Style*, const char*, const char*, int priority);
    void load_path(
	Style*, const char*, const char*, const char*, int priority
    );
    const char* home();

    void init_display(Display* display = nil);
    void connect(Display*);
    void set_style(Display*);
    boolean check(Event&);
};

Session* SessionRep::instance_ = nil;

/*
 * SessionIOHandler is a helper class to translate input ready for a
 * display fd into a call to SessionRep::handle_display_input(Display*);
 */

class SessionIOHandler : public IOHandler {
public:
    SessionIOHandler(SessionRep*, Display*);

    virtual int inputReady(int);
private:
    SessionRep* session_;
    Display* display_;
};

SessionIOHandler::SessionIOHandler(SessionRep* s, Display* d) {
    session_ = s;
    display_ = d;
}

int SessionIOHandler::inputReady(int) {
    session_->handle_display_input(display_);
    return 0;
}

Session::Session(
    const char* classname, int& argc, char** argv,
    const OptionDesc* opts, const PropertyData* initprops, Display* display
) {
    SessionRep::instance_ = this;
    rep_ = new SessionRep();
    rep_->init(classname, argc, argv, opts, initprops, display);
}

Session::~Session() {
    delete rep_;
}

Session* Session::instance() { 
  if (SessionRep::instance_==nil) {
    int argc=1;
    char *argv[1] = {(char*)"noivsession"};
    SessionRep::instance_ = new Session("noivsession", argc, argv);
  }
  return SessionRep::instance_; 
}

const char* Session::name() const { return rep_->name_->string(); }
const char* Session::classname() const { return rep_->classname_; }
int Session::argc() const { return rep_->argc_; }
char** Session::argv() const { return rep_->argv_; }

Style* Session::style() const {
    SessionRep* s = rep_;
    if (s->default_ != nil) {
	return s->default_->style();
    }
    return s->style_;
}

void Session::default_display(Display* d) { rep_->default_ = d; }
Display* Session::default_display() const { return rep_->default_; }

Display* Session::connect(const String& display_name) {
    Display* d = Display::open(display_name);
    if (d != nil) {
	rep_->connect(d);
    }
    return d;
}

Display* Session::connect(const char* display_name) {
    Display* d = Display::open(display_name);
    if (d != nil) {
	rep_->connect(d);
    }
    return d;
}
    
void Session::disconnect(Display* d) {
    DisplayList& list = *(rep_->displays_);
    long n = list.count();
    for (long i = 0; i < n; i++) {
	if (list.item(i) == d) {
	    Dispatcher::instance().unlink(d->fd());
	    d->close();
	    list.remove(i);
	    break;
	}
    }
}

void SessionRep::handle_display_input(Display* d) {
    if (d->closed()) {
	done_ = true;
	return;
    }
    if (readinput_) {
	/*
	 * I know this loop would better be expressed as a while loop,
	 * but my silly compiler doesn't understand it in that form.
	 */
	Event e;
	for (;;) {
	    if (!d->get(e)) {
		break;
	    }
	    e.handle();
	}
    }
}

/*
 * Main session loop: read events and handle them.
 */

int Session::run() {
    Event e;
    boolean& done = rep_->done_;
    done = false;
    do {
	read(e);
	e.handle();
    } while (!done);
    return 0;
}

/*
 * Map window and run.
 */

int Session::run_window(Window* w) {
    w->map();
    return run();
}

/*
 * Exit the main session loop.
 */

void Session::quit() {
    rep_->done_ = true;
}

// added by ro2m: this is necessary for running your own event processing loop..
void Session::undone() {
    rep_->done_ = false;
}

/*
 * Return loop status.
 */

boolean Session::done() const {
    return rep_->done_;
}

/*
 * Check if an event is pending on any display.
 */

boolean Session::pending() const {
    Event e;
    boolean b = rep_->check(e);
    if (b) {
	e.unread();
    }
    return b;
}

/*
 * Read an event.  Could be from any open display.
 * The redundant-looking test of rep_->done_ is necessary
 * because check might change the value of done_.
 */

void Session::read(Event& e, boolean (*test)()) {
    boolean save = rep_->readinput_;
    rep_->readinput_ = false;
    while (!rep_->done_ && !rep_->check(e) && !rep_->done_) {
	Dispatcher::instance().dispatch();
	if (test && (*test)()) return;
    }
    rep_->readinput_ = save;
}

/*
 * Read an event as above, but time out after a given (sec, usec) delay.
 * Return true if an event was read, false if the time-out expired.
 * (this version also checks test function) 
 */

boolean Session::read(long sec, long usec, Event& e, boolean (*test)()) {
    long sec_left = sec;
    long usec_left = usec;
    boolean save = rep_->readinput_;
    rep_->readinput_ = false;
    while (!rep_->done_ && !rep_->check(e) && !rep_->done_) {
	if (!(sec_left > 0 || usec_left > 0)) {
	    rep_->readinput_ = save;
	    return false;
	}
	Dispatcher::instance().dispatch(sec_left, usec_left);
	if (test && (*test)()) return true;
    }
    rep_->readinput_ = save;
    return true;
}

/*
 * Read an event as above, but time out after a given (sec, usec) delay.
 * Return true if an event was read, false if the time-out expired.
 * (this version checks test function, and updates sec/usec accounting) 
 */

boolean Session::read(long* sec, long* usec, Event& e, boolean (*test)()) {
    boolean save = rep_->readinput_;
    rep_->readinput_ = false;
    while (!rep_->done_ && !rep_->check(e) && !rep_->done_) {
	if (!(*sec > 0 || *usec > 0)) {
	    rep_->readinput_ = save;
	    return false;
	}
	Dispatcher::instance().dispatch(*sec, *usec);
	if (test && (*test)()) return true;
    }
    rep_->readinput_ = save;
    return true;
}

/*
 * Check for a pending event, returning it if there is one.
 */

boolean SessionRep::check(Event& e) {
    DisplayList& list = *displays_;
    long n = list.count();
    for (long i = 0; i < n; i++) {
	Display* d = list.item(i);
	if (d->get(e)) {
	    return true;
	}
    }
    return false;
}

/*
 * Put an event back from whence it came.
 */

void Session::unread(Event& e) {
    e.unread();
}

/*
 * Poll an event (implies the event already has an associated display).
 */

void Session::poll(Event& e) {
    e.poll();
}

SessionRep::SessionRep() {
    done_ = false;
    readinput_ = true;
    displays_ = new DisplayList;
}

SessionRep::~SessionRep() {
    delete name_;
    Resource::unref(style_);
    for (ListItr(DisplayList) i(*displays_); i.more(); i.next()) {
	Display* d = i.cur();
	delete d;
    }
    delete displays_;
    delete[] argv_;
}

void SessionRep::init(
    const char* name, int& argc, char** argv,
    const OptionDesc* opts, const PropertyData* initprops, Display* display
) {
    argc_ = argc;
    argv_ = new char*[argc + 1];
    for (int i = 0; i < argc; i++) {
	argv_[i] = argv[i];
    }
    argv_[argc_] = nil;

    init_style(name, initprops);
    if (opts != nil) {
	parse_args(argc, argv, opts);
    }
    parse_args(argc, argv, defoptions);
    init_display(display);

    Cursor::init();

#ifdef sgi
    if (style_->value_is_on("malloc_debug")) {
	mallopt(M_DEBUG, 1);
    }
#endif
}

/*
 * Parse the argument list, setting any properties that are specified
 * by the option list.  Matching arguments are removed (in-place)
 * from the argument list.
 */

void SessionRep::parse_args(int& argc, char** argv, const OptionDesc* opts) {
    int i;
    int newargc = 1;
    char** newargv = new char*[argc];
    newargv[0] = argv[0];
    for (i = 1; i < argc; i++) {
	boolean matched = false;
	String arg(argv[i]);
	for (const OptionDesc* o = &opts[0]; o->name != nil; o++) {
	    if (match(arg, *o, i, argc, argv)) {
		matched = true;
		break;
	    }
	}
	if (!matched) {
	    newargv[newargc] = argv[i];
	    ++newargc;
	}
    }
    if (newargc < argc) {
	for (i = 1; i < newargc; i++) {
	    argv[i] = newargv[i];
	}
	argc = newargc;
	argv[argc] = nil;
    }
    delete [] newargv;
}

/*
 * See if the given argument matches the option description.
 */

boolean SessionRep::match(
    const String& arg, const OptionDesc& o, int& i, int argc, char** argv
) {
    String opt(o.name);
    if (arg != opt) {
	if (o.style == OptionValueAfter) {
	    int n = opt.length();
	    if (opt == arg.left(n)) {
		style_->attribute(String(o.path), arg.right(n));
		return true;
	    }
	}
	return false;
    }
    String name, value;
    extract(arg, o, i, argc, argv, name, value);
    style_->attribute(name, value);
    return true;
}

/*
 * Extract an attribute <name, value> from a given argument.
 */

void SessionRep::extract(
    const String& arg, const OptionDesc& o, int& i, int argc, char** argv,
    String& name, String& value
) {
    int colon;
    switch (o.style) {
    case OptionPropertyNext:
	value = next_arg(i, argc, argv, "missing property after '%s'", arg);
	colon = value.index(':');
	if (colon < 0) {
	    bad_arg("missing ':' in '%s'", value);
	} else {
	    name = value.left(colon);
	    value = value.right(colon+1);
	}
	break;
    case OptionValueNext:
	name = o.path;
	value = next_arg(i, argc, argv, "missing value after '%s'", arg);
	break;
    case OptionValueImplicit:
	name = o.path;
	value = o.value;
	break;
    case OptionValueIsArg:
	name = o.path;
	value = arg;
	break;
    case OptionValueAfter:
	bad_arg("missing value in '%s'", arg);
	break;
    }
}

/*
 * Report that an argument is bad and exit.  A caller of this function
 * may assume that it does not return.
 *
 * We also assume that arg is null-terminated (because it came
 * from argv).
 */

void SessionRep::bad_arg(const char* fmt, const String& arg) {
    fflush(stdout);
    fprintf(stderr, fmt, arg.string());
    putc('\n', stderr);
    exit(1);
}

/*
 * Make sure there is another argument--if not generate an error.
 */

String SessionRep::next_arg(
    int& i, int argc, char** argv, const char* message, const String& arg
) {
    ++i;
    if (i == argc) {
	bad_arg(message, arg);
    }
    return String(argv[i]);
}

/*
 * Find the value for a specific argument.
 */

boolean SessionRep::find_arg(const String& arg, String& value) {
    int last = argc_ - 1;
    for (int i = 1; i < last; i++) {
	if (arg == argv_[i]) {
	    value = String(argv_[i+1]);
	    return true;
	}
    }
    return false;
}

/*
 * Initialize style information for the session.
 */

void SessionRep::init_style(const char* name, const PropertyData* props) {
    classname_ = name;
    name_ = find_name();
    style_ = new Style(*name_);
    Resource::ref(style_);
    style_->alias(classname_);
    props_ = props;
}

/*
 * Set the style information for the given display.
 * First, copy the current style (just the parsed command-line arguments),
 * then add the environment style, display defaults, app defaults,
 * session properties, and default properties.
 */

void SessionRep::set_style(Display* d) {
    Style* s = new Style(*style_);
    load_props(s, defpropvalues, -5);
    load_path(s, ABSLIBALLDIR, "/InterViews", -5);
    load_path(s, RELLIBALLDIR, "/InterViews", -5);
    load_props(s, props_, -5);
    load_app_defaults(s, -5);
    String str;
    if (d->defaults(str)) {
	s->load_list(str, -5);
    } else {
	load_path(s, home(), "/.Xdefaults", -5);
    }
    load_environment(s, -5);
    d->style(s);
}

void SessionRep::load_props(
    Style* s, const PropertyData* props, int priority
) {
    if (props != nil) {
	for (const PropertyData* p = &props[0]; p->path != nil; p++) {
	    s->attribute(String(p->path), String(p->value), priority);
	}
    }
}

void SessionRep::load_app_defaults(Style* s, int priority) {
    load_path(s, X_LIBDIR, "/X11/app-defaults/", classname_, priority);
    load_path(s, ABSLIBALLDIR, "/", classname_, priority);
    load_path(s, RELLIBALLDIR, "/", classname_, priority);
    const char* xres = getenv("XAPPLRESDIR");
    if (xres != nil) {
	load_path(s, xres, "/", classname_, priority);
    } else {
	load_path(s, home(), "/", classname_, priority);
    }
}

void SessionRep::load_environment(Style* s, int priority) {
    const char* xenv = getenv("XENVIRONMENT");
    if (xenv != nil) {
	s->load_file(String(xenv), priority);
    } else {
	load_path(s, ".Xdefaults-", Host::name(), priority);
    }
}

void SessionRep::load_path(
    Style* s, const char* head, const char* tail, int priority
) {
    String h(head);
    String t(tail);
    char* buf = new char[h.length() + t.length() + 1];
    sprintf(buf, "%s%s", h.string(), t.string());
    s->load_file(String(buf), priority);
    delete[] buf;
}

const char* SessionRep::home() {
    const char* home = getenv("HOME");
    if (home == nil) {
	home = ".";
    }
    return home;
}

void SessionRep::load_path(
    Style* s, const char* head, const char* middle, const char* tail,
    int priority
) {
    String h(head);
    String m(middle);
    String t(tail);
    char* buf = new char[h.length() + m.length() + t.length() + 1];
    sprintf(buf, "%s%s%s", h.string(), m.string(), t.string());
    s->load_file(String(buf), priority);
    delete[] buf;
}

/*
 * Use ICCCM rules to find an application's instance name
 * from the command line or an environment variable.
 */

String* SessionRep::find_name() {
    String name;
    if (find_arg(String("-name"), name)) {
	return new String(name);
    }

    const char* res_name = getenv("RESOURCE_NAME");
    if (res_name != nil) {
	return new String(res_name);
    }

    if (argc_ > 0) {
	String s(argv_[0]);
	int slash = s.rindex('/');
	if (slash >= 0) {
	    s = s.right(slash + 1);
	}
	return new String(s);
    }

    return new String("noname");
}

/*
 * Open the default display and initialize its style information.
 */

void SessionRep::init_display(Display* display) {
    String name;

    if (display != nil) {
        default_ = display;
    } else if (style_->find_attribute(String("display"), name)) {
	default_ = Display::open(name);
    } else {
        const char* defaultvar = (char*)getenv( "DISPLAY" );
	default_ = Display::open(defaultvar ? defaultvar : ":0.0");
    }
    if (default_ == nil) {
	if (name.length() > 0) {
	    fprintf(
		stderr, "can't open display %.*s\n",
		name.length(), name.string()
	    );
	} else {
	    fprintf(stderr, "can't open DISPLAY\n");
	}
	exit(1);
    }
    connect(default_);
}

void SessionRep::connect(Display* d) {
    set_style(d);
    Dispatcher::instance().link(
	d->fd(), Dispatcher::ReadMask, new SessionIOHandler(this, d)
    );
    displays_->append(d);
}
