/*

    Minimum Profit 5.x
    A Programmer's Text Editor

    Editing.

    Copyright (C) 1991-2012 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://triptico.com

*/


/** editor actions **/

mp_doc.actions['insert_line'] = sub (d) {
    d->store_undo()->insert_newline();

    if (d.syntax == NULL)
        d->detect_syntax();

    return d;
};

mp_doc.actions['delete_line']       = sub (d) { d->store_undo()->delete_line(); };
mp_doc.actions['insert_space']      = sub (d) { d->store_undo()->insert_space(); };
mp_doc.actions['insert_tab']        = sub (d) { d->store_undo()->insert_tab(); };
mp_doc.actions['insert_real_tab']   = sub (d) { d->store_undo()->insert("\t"); };
mp_doc.actions['delete']            = sub (d) { d->store_undo()->delete_char(); };

mp_doc.actions['delete_left']	= sub (d) {
    if (d.txt.x + d.txt.y)
        d->store_undo()->move_left()->delete_char();

    return d;
};

mp_doc.actions['indent_block'] = sub (d) {
    d->store_undo();

    if (d.txt.mark == NULL)
        d->set_x(0)->insert_tab();
    else {
        local currentY = d.txt.y;
        local startY   = d.txt.mark.by;
        local endY     = d.txt.mark.ey;
        local times    = endY - startY;
	
        d->unmark()->set_y(startY);

        /* use to be while d.txt.y <= endY, but that entered an endless loop when
            you were indenting a block including the very last line in the file */
        while (times >= 0) {
            d->set_x(0)->insert_tab()->move_down();
		
            times--;
        }
	
        d->set_y(startY)->set_x(0)->mark();
	
        d->set_y(endY)->move_eol()->mark();
	
        d->set_y(currentY);
    }

    return d;
};

mp_doc.actions['unindent_block'] = sub(d) {
    d->store_undo();

    if (d.txt.mark == NULL) {
        d->unindent_line();
    }
    else {
        local currentY = d.txt.y;
        local startY   = d.txt.mark.by;
        local endY     = d.txt.mark.ey;
        local times    = endY - startY;

        d->unmark();

        d->set_y(startY);

        /* use to be while d.txt.y <= endY, but that entered an endless loop when
            you were unindenting a block including the very last line in the file */
        while (times >= 0) {
            d->unindent_line()->move_down();
		
            times--;
        }
	
        d->set_y(startY)->set_x(0)->mark();
	
        d->set_y(endY)->move_eol()->mark();
	
        d->set_y(currentY);
    }

    return d;
};

mp_doc.actions['undo'] = sub (d) { d->undo(); };
mp_doc.actions['redo'] = sub (d) { d->redo(); };

mp_doc.actions['join_paragraph'] = sub (d) {
    d->store_undo();

    if (d.txt.mark) {
        d->busy(1)->cut();

        /* create a working document */
        local p = new(mp_doc, { name: '<wrk>' });
        p.txt.lines = mp.clipboard;

        /* while not at EOF, word wrap everything */
        while (p.txt.y < size(p.txt.lines) - 1) {
            p->join_paragraph()->move_down()->move_down();
        }

        /* insert the content */
        d->insert(p.txt.lines)->busy(0);
    }
    else
        d->join_paragraph();

    return d;
};

mp_doc.actions['word_wrap_paragraph'] = sub (d) {
    if(mp.config.word_wrap == 0)
        mp.alert(L("Word wrapping must be set"));
    else {
        d->store_undo();

        if (d.txt.mark) {
            d->busy(1)->cut();

            /* create a working document */
            local p = new(mp_doc, { name: '<wrk>' });
            p.txt.lines = mp.clipboard;

            /* while not at EOF, word wrap everything */
            while (p.txt.y < size(p.txt.lines) - 1) {
                p->word_wrap_paragraph()->move_down()->move_down();
            }

            /* insert the content */
            d->insert(p.txt.lines)->busy(0);
        }
        else
            d->word_wrap_paragraph();
    }

    return d;
};

mp.actions['line_options']	= sub {
    /* convert special characters on end of line */
    local lt = mp.backslash_codes(mp.config.eol, 1);

    local t = mp.form(
        [
            {
                label:      L("Word wrap on column (0, no word wrap):"),
                type:       'text',
                value:      mp.config.word_wrap,
                history:    'wordwrap'
            },
            {
                label:      L("Automatic indentation") ~ ':',
                type:       'checkbox',
                value:      mp.config.auto_indent
            },
            {
                label:      L("Line termination") ~ ':',
                value:      lt,
                type:       'text'
            },
            {
                label:      L("Keep original end of lines") ~ ':',
                value:      mp.config.keep_eol,
                type:       'checkbox'
            },
            {
                label:      L("Mark end of lines") ~ ':',
                value:      mp.config.mark_eol,
                type:       'checkbox'
            },
            {
                label:      L("'Smart' move to beginning of line:"),
                value:      mp.config.smart_bol,
                type:       'checkbox'
            },
            {
                label:      L("Show line numbers:"),
                value:      mp.config.show_line_numbers,
                type:       'checkbox'
            }
        ]
    );

    if (t != NULL) {
        mp.config.word_wrap     = t[0];
        mp.config.auto_indent   = t[1];
        mp.config.eol           = mp.backslash_codes(t[2], 0);
        mp.config.keep_eol      = t[3];
        mp.config.mark_eol      = t[4];
        mp.config.smart_bol     = t[5];
        mp.config.show_line_numbers = t[6];
    }
};

mp.actions['tab_options']	= sub {
	local t = mp.form( [
		{ 'label'	=> L("Tab size") ~ ':',
		  'type'	=> 'text',
		  'value'	=> mp.config.tab_size,
		  'history'	=> 'tabsize' },
		{ 'label'	=> L("Convert tabs to spaces") ~ ':',
		  'type'	=> 'checkbox',
		  'value'	=> mp.config.tabs_as_spaces },
		{ 'label'	=> L("Use previous line for tab columns") ~ ':',
		  'type' 	=> 'checkbox',
		  'value'	=> mp.config.dynamic_tabs }
	] );

	if (t != NULL) {
		mp.config.tab_size       = t[0];
		mp.config.tabs_as_spaces = t[1];
		mp.config.dynamic_tabs   = t[2];
	}
};

mp.actions['toggle_insert'] = sub {
    mp.config.insert = !mp.config.insert;
};

/** default key bindings **/

mp_doc.keycodes['enter']            = "insert_line";
mp_doc.keycodes['tab']              = "insert_tab";
mp_doc.keycodes['shift-tab']        = "insert_real_tab";
mp_doc.keycodes['space']            = "insert_space";
mp_doc.keycodes['delete']           = "delete";
mp_doc.keycodes['backspace']        = "delete_left";
mp_doc.keycodes['ctrl-i']           = "tab";
mp_doc.keycodes['ctrl-m']           = "enter";
mp_doc.keycodes['ctrl-y']           = "delete_line";
mp_doc.keycodes['alt-cursor-right'] = "indent_block";
mp_doc.keycodes['alt-cursor-left']  = "unindent_block";
mp_doc.keycodes['ctrl-z']           = "undo";
mp_doc.keycodes['f4']               = "word_wrap_paragraph";
mp_doc.keycodes['insert']           = "toggle_insert";

/** action descriptions **/

mp.actdesc['insert_line']           = LL("Insert line");
mp.actdesc['delete_line']           = LL("Delete line");
mp.actdesc['insert_space']          = LL("Insert space");
mp.actdesc['insert_tab']            = LL("Insert tab");
mp.actdesc['insert_real_tab']       = LL("Insert real tab character");
mp.actdesc['delete']                = LL("Delete character");
mp.actdesc['delete_left']           = LL("Delete character to the left");
mp.actdesc['indent_block']          = LL("Indent block");
mp.actdesc['unindent_block']        = LL("Unindent block");
mp.actdesc['undo']                  = LL("Undo");
mp.actdesc['redo']                  = LL("Redo");
mp.actdesc['join_paragraph']        = LL("Join a paragraph in one line");
mp.actdesc['word_wrap_paragraph']   = LL("Word-wrap a paragraph");
mp.actdesc['line_options']          = LL("Line options...");
mp.actdesc['tab_options']           = LL("Tab options...");
mp.actdesc['toggle_insert']         = LL("Toggle insert/overwrite mode");

/** code **/

/**
 * mp_doc.break_line - Breaks current line in two (inserts a newline).
 * @doc: the document
 * @col: column where the newline will be inserted 
 *
 * Breaks current line in two by inserting a newline character in between.
 * If @col is not NULL, the newline will be inserted in that column; otherwise,
 * the current x position will be used.
 */
sub mp_doc.break_line(doc, col)
{
    local txt = doc.txt;
    local c, w;

    /* if col is NULL, set it to be the x cursor */
    if (col == NULL)
        col = txt.x;

    /* gets line where cursor is */
    c = txt.lines[txt.y];

    /* deletes from col to the end of line */
    w = splice(c, NULL, col, -1);

    /* set first part as current line */
    txt.lines[txt.y] = w[0];

    /* move to next line */
    txt.y++;

    /* insert a new line here */
    expand(txt.lines, txt.y, 1);

    /* fix the x cursor position */
    txt.x -= col;

    /* if autoindenting... */
    if (mp.config.auto_indent) {
        /* extract leading blanks in the original line
           to prepend them to the line to be inserted */
        local i = regex(c, "/^[ \t]*[-\+\*]?[ \t]+/", 0);

        /* substitute all non-tab characters with spaces */
        i = sregex(i, "/[^\t]/g", " ");

        /* delete any blank in the new line */
        w[1] = sregex(w[1], "/^[ \t]*/");

        /* concatenate */
        w[1] = i ~ w[1];

        /* the x position is further the length of that */
        txt.x += size(i);
    }

    /* put second part there (or an empty string if NULL) */
    txt.lines[txt.y] = w[1] || '';

    txt.mod++;

    return doc;
}


sub mp_doc.join_line(doc)
/* joins the current line with the next one */
{
    local txt = doc.txt;

    if (txt.y < size(txt.lines)) {
        /* concats current line with the next one */
        txt.lines[txt.y] = txt.lines[txt.y] ~ txt.lines[txt.y + 1];

        /* delete it */
        adel(txt.lines, txt.y + 1);

        txt.mod++;
    }

    return doc;
}


sub mp_doc.delete_line(doc)
/* deletes the current line */
{
    local txt = doc.txt;
    local vx;

    /* take current position */
    vx = mp_c.x2vx(txt.lines[txt.y], txt.x);

    /* if it's the only line, just replace it */
    if (size(txt.lines) == 1)
        txt.lines[0] = '';
    else {
        /* destroy the line */
        adel(txt.lines, txt.y);
    }

    /* fix if it was the last line */
    if (txt.y >= size(txt.lines))
        txt.y = size(txt.lines) - 1;

    /* move to previous x position */
    txt.x = mp_c.vx2x(txt.lines[txt.y], vx);

    txt.mod++;

    return doc;
}


sub mp_doc.delete_char(doc)
/* deletes the current char */
{
    local txt = doc.txt;

    if (txt.mark != NULL) {
        doc->delete_mark();
        return;
    }

    /* is it over the end of line? */
    if (txt.x == size(txt.lines[txt.y]))
        doc->join_line();
    else {
        local w = splice(txt.lines[txt.y], NULL, txt.x, 1);
        txt.lines[txt.y] = w[0];
    }

    txt.mod++;

    return doc;
}


sub mp_doc.delete_range(doc, bx, by, ex, ey, v)
/* deletes a range of characters from a document */
{
    local txt = doc.txt;

    /* move to the start of the range */
    txt.x = bx;
    txt.y = by;

    if (by == ey) {
        local w;

        /* block is just one line; delete the middle part */
        w = splice(txt.lines[by], NULL, bx, ex - bx);

        txt.lines[by] = w[0];
    }
    else {
        /* block has more than one line */
        local w;

        if (v == 0) {
            /* delete using normal selection block */

            /* delete from the beginning to the end of the first line */
            w = splice(txt.lines[by], NULL, bx, -1);
            txt.lines[by] = w[0];

            /* delete from the beginning of the last line to
               the end of the block */
            w = splice(txt.lines[ey], NULL, 0, ex);
            txt.lines[ey] = w[0];

            /* collapse the lines in between */
            collapse(txt.lines, by + 1, ey - by - 1);

            /* finally join both lines */
            doc->join_line();
        }
        else {
            /* delete using vertical selection block */
            while (by <= ey) {
                w = splice(txt.lines[by], NULL, bx, ex - bx);
                txt.lines[by] = w[0];
                by++;
            }
        }
    }

    txt.mod++;

    return doc;
}


sub mp_doc.insert_string(doc, str)
/* inserts a string into the cursor position */
{
    local txt = doc.txt;
    local w;

    doc->delete_mark();

    /* splice and change */
    w = splice(txt.lines[txt.y], str, txt.x, mp.config.insert && size(str) || 0);
    txt.lines[txt.y] = w[0];

    /* move right */
    txt.x += size(str);

    txt.mod++;

    return doc;
}


sub mp_doc.insert(doc, a)
/* inserts an array of text into a document */
{
    local txt = doc.txt;
    local s;

    /* if a is not an array, split it */
    if (!is_array(a))
        a = split(a, "\n");

    /* empty array? return */
    if ((s = size(a)) == 0)
        return doc;

    /* paste first line into current position */
    doc->insert_string(a[0]);

    /* more than just one line? */
    if (s > 1) {
        /* break current line in two */
        doc->break_line();

        /* insert last line */
        doc->insert_string(a[s - 1]);
    }

    /* more than two lines? */
    if (s > 2) {
        local n = 1;

        /* open room */
        expand(txt.lines, txt.y, s - 2);

        /* transfer middle lines */
        while (n < s - 1)
            txt.lines[txt.y++] = a[n++];
    }

    return doc;
}


sub mp_doc.wrap_words(doc)
/* do the word wrapping */
{
    local txt = doc.txt;

    if (mp.config.word_wrap) {
        /* take the column where the cursor is */
        local c = mp_c.x2vx(txt.lines[txt.y], txt.x);

        if (c >= mp.config.word_wrap &&
            regex(txt.lines[txt.y], "/^.{1," ~ mp.config.word_wrap ~ "}[ \t]/")) {
            local w;

            /* take the coordinates */
            w = regex();

            /* break the line there */
            doc->break_line(w[1]);

            /* delete the space at the end of the line */
            txt.lines[txt.y - 1] = sregex(txt.lines[txt.y - 1], "/[ \t]$/", NULL);
        }
    }

    return doc;
}


sub mp_doc.insert_space(doc)
/* inserts a space, taking wordwrapping into account */
{
    doc->wrap_words()->insert(' ');
}


sub mp_doc.insert_tab(doc)
/* inserts a tab */
{
    if (mp.config.dynamic_tabs) {
        local y = doc.txt.y;

        while (y > 0) {
            local l = splice(doc.txt.lines[--y], NULL, 0, doc.txt.x);

            if (l[0]) {
                doc->insert(regex(l[0], "/^[^ \t]*[ \t]*/")->sregex("/[^ \t]/g", ' '));
                return doc;
            }
        }
    }

    if (mp.config.tabs_as_spaces) {
        /* number of spaces to insert */
        local n = mp.config.tab_size -
            ((doc.txt.x) % mp.config.tab_size);

        while (n--)
            doc->insert(' ');
    }
    else
        doc->insert("\t");

    return doc;
}


sub mp_doc.insert_newline(doc)
/* inserts a newline */
{
    doc->wrap_words()->break_line();
}


sub mp_doc.keypress(doc, key)
/* inserts from a keystroke (with undo) */
{
    if (size(key) == 1) {
        doc->store_undo()->insert(key);
    }
    else
    if (key != NULL) {
        mp.message = {
            timeout:    time() + 2,
            string:     sprintf(L("Unbound keystroke '%s'"), key)
        };
    }

    return doc;
}


/** undo **/

sub mp_doc.store_undo(doc)
/* stores the current txt in the undo queue */
{
    queue(doc.undo_q, clone(doc.txt), mp.config.undo_levels);
    doc.redo_q = [];

    return doc;
}


sub mp_doc.undo(doc)
/* undoes last operation */
{
    local txt;

    if (txt = pop(doc.undo_q)) {
        queue(doc.redo_q, clone(doc.txt), mp.config.undo_levels);
        doc.txt = txt;
    }

    return doc;
}


sub mp_doc.redo(doc)
/* redoes last undid operation */
{
    local txt;

    if (txt = pop(doc.redo_q)) {
        queue(doc.undo_q, clone(doc.txt), mp.config.undo_levels);
        doc.txt = txt;
    }

    return doc;
}


/** paragraphs **/

sub mp_doc.join_paragraph(doc)
/* joins current paragraph in just one line */
{
    local txt = doc.txt;
    local l;

    while ((l = txt.lines[txt.y + 1]) && size(l)) {
        /* delete all leading blanks in the next line */
        txt.lines[txt.y + 1] = sregex(txt.lines[txt.y + 1], "/^[ \t]+/");

        /* move to end of line, add a space separator and join */
        doc->move_eol()->insert(' ')->join_line();
    }

    return doc;
}


sub mp_doc.word_wrap_paragraph(doc)
/* word wraps current paragraph */
{
    local txt = doc.txt;

    if (mp.config.word_wrap) {
        doc->join_paragraph()->move_eol();

        while (size(txt.lines[txt.y]) > mp.config.word_wrap) {
            doc->insert_space()->move_left()->delete_char();
        }
    }

    return doc;
}

/* indent/unindent support functions */

sub mp_doc.unindent_line(d)
/* Unindent the current line by 1 tab or the indent size */
{
    local l = split(d.txt.lines[d.txt.y]);
	
    d->set_x(0);
	
    if (cmp(l[0], "\t") == 0) {
        d->delete_char();
    }
    else {
        local i = 0;
        while (i < mp.config.tab_size) {
            if (cmp(l[i], " ") == 0) {
                d->delete_char();
            }
            else {
                break;
            }
			
            i++;
        }
    }

    return d;
}
