/*

    Minimum Profit 5.x
    A Programmer's Text Editor

    File manipulation.

    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

*/

/* editor actions */

mp.actions['new']	= sub (d) {
	d = mp.find_file_by_name(L("<unnamed>"));

	if (d != -1) {
		mp.active_i = d;
		d = mp.active();
	}
	else
		d = mp.new();
};

mp.actions['next']	= sub (d) { mp.next(); };
mp.actions['prev']	= sub (d) { mp.prev(); };

mp.actions['save_as']	= sub (d) {

	local t;

	if ((t = mp.savefile(L("Save file as:"))) == NULL)
		return;

	/* store new name */
	d.name = t;

	if (mp.long_op(mp.save, d) == -1)
		mp.alert(sprintf(L("Error saving file: %s"), ERRNO));
	else
		mp.detect_syntax(d);
};

mp.actions['save']	= sub (d) {

	/* name is <unnamed> or something similar; ask for one */
	if (regex("/^<.+>$/", d.name))
		mp.actions.save_as(d);
	else
	if (mp.long_op(mp.save, d) == -1)
		mp.alert(sprintf(L("Error saving file: %s"), ERRNO));
};

mp.actions['close']	= sub (d) {

	if (d.txt.mod) {
		local r;
		r = mp.confirm(L("File has changed. Save changes?"));

		/* cancel? don't close */
		if (r == 0)
			return;
		if (r == 1)
			mp.actions.save(d);
	}

	mp.close();
};

mp.actions['exit']	= sub (d) {

	local s;

	if (mp.config.auto_sessions)
		mp.save_session();

	if (mp.actions.close_all())
		mp.exit();
};

mp.actions['open']	= sub (d) {

	local n;

	if ((n = mp.openfile(L("File to open:"))) != NULL && n ne "")
		if (mp.long_op(mp.open, n) == NULL && ERRNO != NULL)
			mp.alert(sprintf("Error opening '%s': %s", n, ERRNO));
};

mp.actions['revert']	= sub (d) {
	/* save current name */
	local p = d.name;

	if (d.txt.mod) {
		local r;
		r = mp.confirm(L("File has changed. Are you sure?"));

		/* cancel? don't close */
		if (r == 0 || r == 2)
			return;
	}

	mp.close();
	if (mp.long_op(mp.open, p) == NULL && ERRNO != NULL)
		mp.alert(sprintf("Error opening '%s': %s", p, ERRNO));
};

mp.actions['open_config_file']	= sub (d) {

	mp.open(HOMEDIR ~ ".mp.mpsl");
};

mp.actions['sync'] = sub (d) {

	/* save all modified documents */
	foreach (d, grep(sub (e) { e.txt.mod; }, mp.docs))
		mp.actions.save(d);
};

mp.actions['exec_command']	= sub (d) {

	local t = mp.form( [
		{ 'label' => L("System command:"),
		  'type' => 'text',
		  'history' => 'system' }
		]);

	if (t != NULL) {
		local cmd = t[0];

		/* does it start with a pipe? */
		if (regex('/^\|/', cmd)) {
			local p;

			/* yes; current document should be fed to it */
			cmd = sregex('/^\|/', cmd, NULL);

			if ((p = popen(cmd, "w")) != NULL) {
				foreach (l, mp.get_active_area(d))
					write(p, l ~ mp.config.eol);

				pclose(p);
			}
			else
				mp.drv.alert(
					sprintf(L("Error writing to command '%s'"), cmd));
		}
		else {
			/* no; execute command and insert into cursor */
			local p;

			if ((p = popen(cmd, "r")) != NULL) {
				local l;

				mp.store_undo(d);

				while ((l = read(p)) != NULL)
					mp.insert(d, l);

				pclose(p);
			}
			else
				mp.drv.alert(
					sprintf(L("Error reading from command '%s'"), cmd));
		}
	}
};

mp.actions['close_all'] = sub {

	local s;

	while (s = size(mp.docs)) {
		local doc = mp.docs[mp.active_i];
		
		/* close current document */
		mp.actions.close(doc);

		/* if the size of the list hasn't changed,
		   action was cancelled, so don't exit */
		if (s == size(mp.docs))
			return 0;
	}

	return 1;
};

/* default key bindings */

mp.keycodes['ctrl-n']		= 'next';
mp.keycodes['ctrl-o']		= 'open';
mp.keycodes['ctrl-q']		= 'exit';
mp.keycodes['ctrl-s']		= 'save';
mp.keycodes['ctrl-w']		= 'close';

mp.keycodes['close-window'] 	= 'exit';

/* action descriptions */

mp.actdesc['new']	= LL("New");
mp.actdesc['save']	= LL("Save...");
mp.actdesc['save_as']	= LL("Save as...");
mp.actdesc['next'] 	= LL("Next");
mp.actdesc['prev'] 	= LL("Previous");
mp.actdesc['open'] 	= LL("Open...");
mp.actdesc['exit'] 	= LL("Exit");
mp.actdesc['close'] 	= LL("Close");
mp.actdesc['revert']	= LL("Revert");
mp.actdesc['close_all']	= LL("Close all");

mp.actdesc['open_config_file']		= LL("Edit configuration file");
mp.actdesc['open_templates_file']	= LL("Edit templates file");
mp.actdesc['sync']			= LL("Save modified texts");
mp.actdesc['exec_command']		= LL("Run system command...");

/* code */

sub mp.chomp(str)
/* chomps the end of file chars from a string */
{
	sregex("/\r?\n$/", str, NULL);
}


sub mp.open_file_for_reading(filename)
/* the three-state file opening of text editors: open if possible,
   fail on errors, create new if non-existent */
{
	local f;

	/* clear previous errors */
	ERRNO = NULL;

	if ((f = open(filename, "rb")) == NULL) {
		/* save ERRNO */
		local e = ERRNO;

		/* if a stat() can be done, it means the file
		   exists and can't be open; this is an error.
		   otherwise, it's a non-existent file, so not
		   an error for a text editor */
		if (stat(filename) != NULL)
			ERRNO = e;
		else {
			ERRNO = NULL;

			mp.message = {
				'timeout' => time() + 2,
				'string'  => sprintf(L("New file '%s'"), filename)
			};
		}
	}

	return f;
}


sub mp.save(doc)
/* saves a file */
{
	local f;
	local s = NULL;
	local nl = 0;

	/* if unlink before write is desired, do it */
	if (mp.config.unlink && (s = stat(doc.name)) != NULL)
		unlink(doc.name);

	if ((f = open(doc.name, "wb")) == NULL) {
		/* can't write? delete name */
		doc.name = L("<unnamed>");
		return -1;
	}

	/* if the document has a password, save it encrypted */
	if (doc.password)
		nl = mp.crypt1_save(f, doc.txt.lines, doc.password);
	else {
		/* save as a plain text file */
		foreach (l, doc.txt.lines) {
			/* write a line separator if it's not the first line */
			if (nl)
				write(f, mp.config.eol);

			write(f, l);
			nl++;
		}
	}

	close(f);

	doc.txt.mod = 0;

	/* set back the permissions and ownership, if available */
	if (s != NULL) {
		chmod(doc.name, s[2]);
		chown(doc.name, s[4], s[5]);
	}

	return nl;
}


sub mp.new(filename, lines)
/* creates a new document */
{
	local doc, txt;

	txt = {};
	txt.x = 0;
	txt.y = 0;
	txt.vx = 0;
	txt.vy = 0;
	txt.lines = lines || [ '' ];
	txt.mod = 0;

	doc = {};
	doc.name = filename || L("<unnamed>");
	doc.txt = txt;

	doc.undo = [];
	doc.redo = [];

	doc.syntax = NULL;

	/* store in the list and set as active */
	push(mp.docs, doc);
	mp.active_i = size(mp.docs) - 1;

	/* autodetect syntax */
	mp.detect_syntax(doc);

	return doc;
}


sub mp.next()
/* rotates through the document list */
{
	if (++mp.active_i == size(mp.docs))
		mp.active_i = 0;
}


sub mp.prev()
/* rotates through the document list, backwards */
{
	if (--mp.active_i == -1)
		mp.active_i = size(mp.docs) - 1;
}


sub mp.close()
/* closes the active document */
{
	local k = mp.active_i;

	/* delete from the list */
	adel(mp.docs, mp.active_i);

	/* rotate if it was the last one */
	if (mp.active_i == size(mp.docs))
		mp.active_i = 0;
}


sub mp.find_file_by_name(filename)
/* finds an open file by its name */
{
	seek(map(sub(d) { d.name; }, mp.docs), filename);
}


sub mp.open(filename)
/* opens a new document (uses UI) */
{
	local d = mp.find_file_by_name(filename);

	/* looks first if the file is already open */
	if (d != -1) {
		mp.active_i = d;
		d = mp.active();
	}
	else {
		local f, l, p;

		if ((f = mp.open_file_for_reading(filename)) == NULL) {
			if (ERRNO != NULL)
				return NULL;

			/* file doesn't exist: new document */
		}
		else {
			if (mp.crypt1_detect(f)) {
				/* password needed; ask for it */
				if ((p = mp.form( [
					{ 'label'	=> L("Password:"),
					  'type'	=> 'password' }
					])) == NULL) {
					/* cancel? fail, but not on error */
					ERRNO = NULL;
					return NULL;
				}

				/* get the password */
				p = p[0];

				/* an empty password is equal to cancellation */
				if (p eq '')
					return NULL;

				/* and load the file */
				l = mp.crypt1_load(f, p);
			}
			else
				l = mp.plain_load(f);

			close(f);
		}

		d = mp.new(filename, l);

		/* does it have a password? store it */
		if (p != NULL)
			d.password = p;
	}

	return d;
}
