/*

    Minimum Profit 5.x
    A Programmer's Text Editor

    Copyright (C) 1991-2007 Angel Ortega <angel@triptico.com>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

    http://www.triptico.com

*/

/*******************
	Data
********************/

/* L(x) is the same as gettext(x) */
L = gettext;

/* LL(x) is the same as x */
sub LL(x) { x; }

/* set gettext() domain */
gettext_domain('minimum-profit', APPDIR ~ 'locale');

/* configuration */

mp.config = {};
mp.config.undo_levels = 100;
mp.config.word_wrap = 0;
mp.config.auto_indent = 0;
mp.config.status_format = "%m%n %x,%y [%l] %R%O %s %t";
mp.config.tab_size = 8;
mp.config.tabs_as_spaces = 0;
mp.config.unlink = 1;
mp.config.case_sensitive_search = 1;
mp.config.global_replace = 0;
mp.config.preread_lines = 60;
mp.config.mark_eol = 0;

/* default end of line, system dependent */
if (mp.drv.id eq 'win32')
	mp.config.eol = "\r\n";
else
	mp.config.eol = "\n";

/* a regex for selection words */
mp.word_regex = '/[A-Z_][A-Z0-9_]*/i';

/* document list */
mp.docs = [];
mp.active_i = 0;

/* allowed color names (order matters, match the Unix curses one) */
mp.color_names = [ "default", "black", "red", "green",
	"yellow", "blue", "magenta", "cyan", "white" ];

/* color definitions */
mp.colors = {
	'normal' => {		'text'	=> [ 'default', 'default' ],
				'gui'	=> [ 0x000000, 0xffffff ] },
	'cursor' => {		'text'	=> [ 'default', 'default' ],
				'gui'	=> [ 0x000000, 0xffffff ],
				'flags' => [ 'reverse' ] },
	'selection' => {	'text'	=> [ 'red', 'default' ],
				'gui'	=> [ 0xff0000, 0xffffff ],
				'flags'	=> [ 'reverse'] },
	'comments' => {		'text'	=> [ 'green', 'default' ],
				'gui'	=> [ 0x00cc77, 0xffffff ] },
	'quotes' => {		'text'	=> [ 'blue', 'default' ],
				'gui'	=> [ 0x0000ff, 0xffffff ],
				'flags'	=> [ 'bright' ] },
	'matching' => {		'text'	=> [ 'black', 'cyan' ],
				'gui'	=> [ 0x000000, 0xffff00 ] },
	'word1' => {		'text'	=> [ 'green', 'default' ],
				'gui'	=> [ 0x00aa00, 0xffffff ],
				'flags'	=> [ 'bright' ] },
	'word2' => {		'text'	=> [ 'red', 'default' ],
				'gui'	=> [ 0xff6666, 0xffffff ],
				'flags' => [ 'bright' ] },
	'tag' => {		'text'	=> [ 'cyan', 'default' ],
				'gui'	=> [ 0x8888ff, 0xffffff ],
				'flags'	=> [ 'underline' ] },
	'spell' => {		'text'	=> [ 'red', 'default' ],
				'gui'	=> [ 0xff8888, 0xffffff ],
				'flags'	=> [ 'bright', 'underline' ] },
	'search' => {		'text'	=> [ 'black', 'green' ],
				'gui'	=> [ 0x000000, 0x00cc77 ] }
};

/* hash of specially coloured words */
mp.word_color = {};

mp.keycodes = {
	'ctrl-f5'	=>	sub (d) { mp.insert(d, "1968-08-17"); },

	'ctrl-b'	=>	NULL,
	'ctrl-d'	=>	NULL,
	'ctrl-e'	=>	NULL,
	'ctrl-h'	=>	NULL,
	'ctrl-j'	=>	NULL,
	'ctrl-k'	=>	NULL,
	'ctrl-l'	=>	NULL,
	'ctrl-p'	=>	NULL,
	'ctrl-u'	=>	NULL
};

mp.actions = {};

mp.actdesc = {};

mp.alert_log = [];

/* the menu */
mp.menu = [
	[
		LL("&File"),
		[ 'new', 'open', 'save', 'save_as', 'close', 'revert',
			'-', 'set_password',
			'-', 'open_config_file',
			'-', 'sync',
			'-', 'save_session', 'load_session',
			'-', 'exit'
		]
	],
	[
		LL("&Edit"),
		[ 'undo', 'redo', '-',
			'cut_mark', 'copy_mark', 'paste_mark', 'delete_line', '-',
			'mark',	'unmark', '-',
			'insert_template', '-',
			'word_wrap_paragraph', 'join_paragraph', '-',
			'exec_command', '-',
			'eval', 'eval_doc'
		]
	],
	[
		LL("&Search"),
		[ 'seek', 'seek_next', 'seek_prev', 'replace', '-',
			'find_tag', 'complete', 'complete_symbol', '-', 'grep'
		]
	],
	[
		LL("&Go to"),
		[ 'next', 'prev',
			'move_bof', 'move_eof', 'move_bol', 'move_eol',
			'goto', 'move_word_right', 'move_word_left',
			'-', 'document_list'
		]
	],
	[
		LL("&Options"),
		[ 'record_macro', 'play_macro', '-',
			'encoding', 'tab_options', 'line_options', 'toggle_spellcheck', '-',
			'word_count', '-',
			'zoom_in', 'zoom_out', '-',
			'about'
		]
	]
];

/*******************
	Code
********************/

sub mp.active()
/* returns the active document */
{
	local d;

	/* empty document list? create a new, empty one */
	if (size(mp.docs) == 0)
		mp.new();

	/* get active document */
	d = mp.docs[mp.active_i];

	return d;
}


sub mp.process_action(a)
/* processes an action */
{
	local f, d;

	d = mp.active();

	if ((f = mp.actions[a]) != NULL)
		f(d);
	else {
		mp.message = {
			'timeout'	=> time() + 2,
			'string'	=> sprintf(L("Unknown action '%s'"), a)
		};
	}
}


sub mp.process_event(k)
/* processes a key event */
{
	local d, a;

	/* empty document list? do nothing */
	if (size(mp.docs) == 0)
		return;

	d = mp.active();

	if (mp.keycodes_t == NULL)
		mp.keycodes_t = mp.keycodes;

	/* get the action asociated to the keycode */
	if ((a = mp.keycodes_t[k]) != NULL) {

		/* if it's a hash, store for further testing */
		if (is_hash(a))
			mp.keycodes_t = a;
		else {
			/* if it's executable, run it */
			if (is_exec(a))
				a(d);
			else
			/* if it's an array, process it sequentially */
			if (is_array(a))
				foreach(l, a)
					mp.process_action(l);
			else
				mp.process_action(a);

			mp.keycodes_t = NULL;
		}
	}
	else {
		mp.insert_keystroke(d, k);
		mp.keycodes_t = NULL;
	}

	mp.shift_pressed = NULL;
}


sub mp.build_status_line()
/* returns the string to be drawn in the status line */
{
	if (mp.message) {
		/* is the message still active? */
		if (mp.message.timeout > time())
			return mp.message.string;

		mp.message = NULL;
	}

	local d = mp.active();
	local w = mp.get_word(d);
	local t = mp.tags[w].label;
	local f = {
		"%V"	=>	mp.VERSION,		/* version */
		"%m"	=>	d.txt.mod && '*' || '',	/* modify flag */
		"%x"	=>	d.txt.x + 1,		/* x coord */
		"%y"	=>	d.txt.y + 1,		/* y coord */
		"%l"	=>	size(d.txt.lines),	/* # of lines */
		"%R"	=>	mp.macro.process_event && 'R' || '', /* recording flag */
		"%O"	=>	"",			/* overwrite mode */
		"%s"	=>	d.syntax.name,		/* synhi mode */
		"%t"	=>	t,			/* tag target */
		"%n"	=>	d.name,			/* document name */
		"%%"	=>	"%"			/* escaped % */
	};

	return sregex("/%./g", mp.config.status_format, f);
}


sub mp.backslash_codes(s, d)
/* encodes (d == 0) or decodes (d == 1) backslash codes
   (like \n, \r, etc.) */
{
	d &&	sregex("/[\r\n\t]/g", s, { "\r" => '\r', "\n" => '\n', "\t" => '\t'}) ||
		sregex("/\\[rnt]/g", s, { '\r' => "\r", '\n' => "\n", '\t' => "\t"});
}


sub mp.long_op(func, a1, a2, a3)
/* executes a potentially long function */
{
	local r;

	mp.busy(1);
	r = func(a1, a2, a3);
	mp.busy(0);

	return r;
}


sub mp.get_history(key)
/* returns a history for the specified key */
{
	if (key == NULL)
		return NULL;
	if (mp.history == NULL)
		mp.history = {};
	if (mp.history[key] == NULL)
		mp.history[key] = [];

	return mp.history[key];
}


sub mp.menu_label(action)
/* returns a label for the menu for an action */
{
	local l;

	/* if action is '-', it's a menu separator */
	if (action eq '-')
		return NULL;

	/* no recognized action? return */
	if (!exists(mp.actions, action))
		return action ~ "?";

	/* get the translated description */
	l = L(mp.actdesc[action]) || action;

	/* is there a keycode that generates this action? */
	foreach (i, sort(keys(mp.keycodes))) {
		if (mp.keycodes[i] eq action) {
			/* avoid mouse and window pseudo-keycodes */
			if (!regex("/window/", i) && !regex("/mouse/", i)) {
				l = l ~ ' [' ~ i ~ ']';
				break;
			}
		}
	}

	return l;
}


sub mp.usage()
/* set mp.exit_message with an usage message (--help) */
{
	mp.exit_message = 
	sprintf(L(
		"Minimum Profit %s - Programmer Text Editor\n"\
		"Copyright (C) Angel Ortega <angel@triptico.com>\n"\
		"This software is covered by the GPL license. NO WARRANTY.\n"\
		"\n"\
		"Usage: mp-5 [options] [files...]\n"\
		"\n"\
		"Options:\n"\
		"\n"\
		" -t {tag}           Edits the file where tag is defined\n"\
		" -e {mpsl_code}     Executes MPSL code\n"\
		" -f {mpsl_script}   Executes MPSL script file\n"\
		" +NNN               Moves to line number NNN of last file\n"\
		"\n"\
		"Homepage: http://www.triptico.com/software/mp.html\n"\
		"Mailing list: mp-subscribe@lists.triptico.com\n"
	), mp.VERSION);
}


sub mp.process_cmdline()
/* process the command line arguments (ARGV) */
{
	local o, line;

	mp.load_tags();

	/* skip ARGV[0] */
	shift(ARGV);

	while (o = shift(ARGV)) {
		if (o eq '-h' || o eq '--help') {
			mp.usage();
			mp.exit();
			return;
		}
		else
		if (o eq '-e') {
			/* execute code */
			local c = shift(ARGV);

			if (! regex('/;\s*$/', c))
				c = c ~ ';';

			eval(c);
		}
		else
		if (o eq '-f') {
			/* execute script */
			local s = shift(ARGV);

			if (stat(s) == NULL)
				ERROR = sprintf(L("Cannot open '%s'"), s);
			else {
				mp.open(s);
				eval(join("\n", mp.active.txt.lines));
				mp.close();
			}
		}
		else
		if (o eq '-t')
			mp.open_tag(shift(ARGV));
		else
		if (regex('/^\+/', o)) {
			/* move to line */
			line = o - 1;
		}
		else
			mp.open(o);
	}

	if (ERROR) {
		mp.exit_message = ERROR ~ "\n";
		ERROR = NULL;
		mp.exit();
		return;
	}

	/* if no files are loaded, try a session */
	if (size(mp.docs) == 0 && mp.config.auto_sessions) {
		mp.load_session();
	}
	else {
		/* set the first as the active one */
		mp.active_i = 0;
	}

	mp.active();

	/* if there is a line defined, move there */
	if (line != NULL)
		mp.set_y(mp.active(), line);
}


sub mp.load_profile()
/* loads ~/.mp.mpsl */
{
	/* if /etc/mp.mpsl exists, execute it */
	if (stat('/etc/mp.mpsl') != NULL) {
		eval( sub {
			local INC = [ '/etc' ];
			load('mp.mpsl');
		});
	}

	/* if ~/.mp.mpsl exists, execute it */
	if (stat(HOMEDIR ~ '.mp.mpsl') != NULL) {
		eval( sub {
			local INC = [ HOMEDIR ];
			load(".mp.mpsl");
		});
	}
}


sub mp.normalize_version(vs)
/* converts a version string to something usable with cmp() */
{
	map(sub(e) { sprintf("%03d", e); },
		split('.',
			sregex('/-.+$/', vs)));
}


sub mp.assert_version(found, minimal, package)
/* asserts that 'found' version of 'package' is at least 'minimal',
   or generate a warning otherwise */
{
	if (cmp(mp.normalize_version(found),
		mp.normalize_version(minimal)) < 0) {
		mp.alert(sprintf(L("WARNING: %s version found is %s, but %s is needed"),
				package, found, minimal));
	}
}


sub mp.test_versions()
/* tests component versions */
{
	local mpdm = MPDM();

	mp.assert_version(mpdm.version, '1.0.4', 'MPDM');
	mp.assert_version(MPSL.VERSION, '1.0.4', 'MPSL');
}


load("mp_drv.mpsl");
load("mp_move.mpsl");
load("mp_edit.mpsl");
load("mp_file.mpsl");
load("mp_clipboard.mpsl");
load("mp_search.mpsl");
load("mp_tags.mpsl");
load("mp_syntax.mpsl");
load("mp_macro.mpsl");
load("mp_templates.mpsl");
load("mp_spell.mpsl");
load("mp_misc.mpsl");
load("mp_crypt.mpsl");
load("mp_keyseq.mpsl");
load("mp_session.mpsl");
load("mp_build.mpsl");

/** MAIN **/

mp.load_profile();
mp.drv.startup();
mp.process_cmdline();
mp.test_versions();
mp.drv.main_loop();
mp.drv.shutdown();
