/*******************************************************************************
 * Copyright (c) 2013-2017, Andrés Martinelli <andmarti@gmail.com              *
 * All rights reserved.                                                        *
 *                                                                             *
 * This file is a part of SC-IM                                                *
 *                                                                             *
 * SC-IM is a spreadsheet program that is based on SC. The original authors    *
 * of SC are James Gosling and Mark Weiser, and mods were later added by       *
 * Chuck Martin.                                                               *
 *                                                                             *
 * Redistribution and use in source and binary forms, with or without          *
 * modification, are permitted provided that the following conditions are met: *
 * 1. Redistributions of source code must retain the above copyright           *
 *    notice, this list of conditions and the following disclaimer.            *
 * 2. Redistributions in binary form must reproduce the above copyright        *
 *    notice, this list of conditions and the following disclaimer in the      *
 *    documentation and/or other materials provided with the distribution.     *
 * 3. All advertising materials mentioning features or use of this software    *
 *    must display the following acknowledgement:                              *
 *    This product includes software developed by Andrés Martinelli            *
 *    <andmarti@gmail.com>.                                                    *
 * 4. Neither the name of the Andrés Martinelli nor the                        *
 *   names of other contributors may be used to endorse or promote products    *
 *   derived from this software without specific prior written permission.     *
 *                                                                             *
 * THIS SOFTWARE IS PROVIDED BY ANDRES MARTINELLI ''AS IS'' AND ANY            *
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED   *
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE      *
 * DISCLAIMED. IN NO EVENT SHALL ANDRES MARTINELLI BE LIABLE FOR ANY           *
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES  *
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;*
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND *
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT  *
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE       *
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.           *
 *******************************************************************************/

/**
 * \file undo.c
 * \author Andrés Martinelli <andmarti@gmail.com>
 * \date 2017-07-18
 * \brief TODO Write a tbrief file description.
 */

#ifdef UNDO
/*
 * UNDO and REDO features works with an 'undo' struct list.
 * Which contains:
 *       p_ant: pointer to 'undo' struct. If NULL, this node is the first change
 *              for the session.
 *       struct ent * added: 'ent' elements added by the change
 *       struct ent * removed: 'ent' elements removed by the change
 *       struct ent * aux_ents: 'ent' elements that needs to be around, but out of tbl.
 *                               these are used to update formulas correctly, upon shift/deletion of ents.
 *       struct undo_range_shift * range_shift: range shifted by change
 *       row_hidded: integers list (int *) hidden rows on screen
 *       row_showed: integers list (int *) visible rows on screen
 *       col_hidded: integers list (int *) hidden columns on screen
 *       col_showed: integers list (int *) visible columns on screen
 *              NOTE: the first position of the lists contains (number of elements - 1) in the list
 *       struct undo_cols_format * cols_format: list of 'undo_col_info' elements used for
 *              undoing / redoing changes in columns format (fwidth, precision y realfmt)
 *       p_sig: pointer to 'undo' struct, If NULL, this node is the last change in
 *              the session.
 *
 * Follows one level UNDO/REDO scheme. A change (C1) is made, then an UNDO operation, and
 * another change (C2). From there later changes are removed.
 * Scheme:
 *
 * + C1 -> + -> UNDO -
 * ^                 \
 * |_                |
 *   \---------------/
 * |
 * |
 * |
 *  \-> C2 --> + ...
 *
 * undo_shift_range struct contains:
 *     int delta_rows: delta rows for the range shift
 *     int delta_cols: delta columns for the range shift
 *     int tlrow:      Upper left row defining the range of the shift
 *                     (As if a cell range shift is made)
 *     int tlcol:      Upper left column defining the range of the shift
 *                     (As if a cell range shift is made)
 *     int brrow:      Lower right row defining the range of the shift
 *                     (As if a cell range shift is made)
 *     int brcol:      Lower right column defining the range of the shift
 *                     (As if a cell range shift is made)
 *
 * Implemented actions for UNDO/REDO:
 * 1. Remove content from cell or range
 * 2. Input content into a cell
 * 3. Edit a cell
 * 4. Change alginment of range or cell
 * 5. Paste range or cell
 * 6. Shift range or cell with sh, sj, sk, sl
 * 7. Insert row or column
 * 8. Delete row or column
 * 9. Paste row or column
 * 10. Hide/show rows and columns
 * 11. Sort of a range
 * 12. Change in the format of a range or cell
 * 13. '-' and '+' commands in normal mode
 * 14. Lock and unlock of cells
 * 15. datefmt command
 * 16. Change in format of a column as a result of the 'f' command
 * 17. Change in format of a column as a result of auto_jus
 * 18. Change format of columns as a result of ic dc
 * 19. fill command
 * 20. unformat
 *
 * NOT implemented:
 * 1. undo of freeze / unfreeze command
 */

#include <stdlib.h>
#include "undo.h"
#include "macros.h"
#include "curses.h"
#include "conf.h"
#include "sc.h"
#include "cmds.h"
#include "marks.h"
#include "shift.h"
#include "dep_graph.h"

// undolist
static struct undo * undo_list = NULL;

// current position in the list
static int undo_list_pos = 0;

// Number of elements in the list
static int undo_list_len = 0;

// Temporal variable
static struct undo undo_item;

/**
 * \brief Init 'unto_item'
 *
 * \return none
 */

void create_undo_action() {
    undo_item.added       = NULL;
    undo_item.removed     = NULL;
    undo_item.p_ant       = NULL;
    undo_item.p_sig       = NULL;
    undo_item.range_shift = NULL;
    undo_item.cols_format = NULL;
    undo_item.aux_ents    = NULL;

    undo_item.row_hidded  = NULL;
    undo_item.row_showed  = NULL;
    undo_item.col_hidded  = NULL;
    undo_item.col_showed  = NULL;
    return;
}

/**
 * @brief TODO Document end_undo_action()
 *
 * \details Save undo_item copy with 'ent' elements modified, and the
 * unto range shift struct into the undolist.
 *
 * \return none
 */

void end_undo_action() {
    add_to_undolist(undo_item);

    // in case we need to dismissed this undo_item!
    if ((undo_item.added      == NULL && undo_item.aux_ents == NULL &&
        undo_item.removed     == NULL && undo_item.range_shift == NULL &&
        undo_item.row_hidded  == NULL && undo_item.row_showed  == NULL &&
        undo_item.cols_format == NULL &&
        undo_item.col_hidded  == NULL && undo_item.col_showed  == NULL) || loading) {
        if (undo_list->p_ant != NULL) undo_list = undo_list->p_ant;
        undo_list_pos--;
        clear_from_current_pos();
    }

    return;
}

/**
 * \brief Document add_to_undolist()
 *
 * \details Add an undo node to the undolist. Allocate memory for
 * undo struct. Fill variable with undo_item value and append it
 * to the list.
 *
 * \param[in] u
 *
 * \return none
 */

void add_to_undolist(struct undo u) {
    // If not at the end of the list, remove from the end
    if ( undo_list != NULL && undo_list_pos != len_undo_list() )
        clear_from_current_pos();

    struct undo * ul = (struct undo *) malloc (sizeof(struct undo));
    ul->p_sig = NULL;

    // Add 'ent' elements
    ul->added = u.added;
    ul->removed = u.removed;
    ul->aux_ents = u.aux_ents;
    ul->range_shift = u.range_shift;
    ul->cols_format = u.cols_format;
    ul->row_hidded = u.row_hidded;
    ul->col_hidded = u.col_hidded;
    ul->row_showed = u.row_showed;
    ul->col_showed = u.col_showed;

    if (undo_list == NULL) {
        ul->p_ant = NULL;
        undo_list = ul;
    } else {
        ul->p_ant = undo_list;

        // go to end of list
        while (undo_list->p_sig != NULL) undo_list = undo_list->p_sig;

        undo_list->p_sig = ul;
        undo_list = undo_list->p_sig;
    }
    undo_list_pos++;
    undo_list_len++;
    return;
}

/**
 * \brief Dismiss current undo_item
 *
 * \details This function frees memory of a struct unto. It is used
 * in function free_undo_node for that purpose. But as well, this 
 * function shall be called instead of end_undo_action in case we want
 * to cancel a previous create_undo_action. If called for this purpose,
 * argument shall be NULL.
 *
 * \param[in] ul
 *
 * \return none
 */

void dismiss_undo_item(struct undo * ul) {
    struct ent * en;
    struct ent * de;

    if (ul == NULL) ul = &undo_item;

    en = ul->added;     // free added
    while (en != NULL) {
        de = en->next;
        clearent(en);
        free(en);
        en = de;
    }
    en = ul->removed;   // free removed
    while (en != NULL) {
        de = en->next;
        clearent(en);
        free(en);
        en = de;
    }
    en = ul->aux_ents; // free aux_ents
    while (en != NULL) {
        de = en->next;
        clearent(en);
        free(en);
        en = de;
    }
    if (ul->range_shift != NULL) free(ul->range_shift); // Free undo_range_shift memory
    if (ul->cols_format != NULL) {                      // Free cols_format memory
        free(ul->cols_format->cols);
        free(ul->cols_format);
    }
    if (ul->row_hidded  != NULL) free(ul->row_hidded); // Free hidden row memory
    if (ul->col_hidded  != NULL) free(ul->col_hidded); // Free hidden col memory
    if (ul->row_showed  != NULL) free(ul->row_showed); // Free showed row memory
    if (ul->col_showed  != NULL) free(ul->col_showed); // Free showed col memory

    return;
}

/**
 * \brief Cascade free UNDO node memory
 *
 * \param[in] ul
 *
 * \return none
 */

void free_undo_node(struct undo * ul) {
    struct undo * e;

    // Remove from current position
    while (ul != NULL) {
        dismiss_undo_item(ul);

        e = ul->p_sig;
        free(ul);
        undo_list_len--;
        ul = e;
    }
    return;
}

/**
 * \brief Remove nodes below the current position from the undolist
 *
 * \return none
 */

void clear_from_current_pos() {
    if (undo_list == NULL) return;

    if (undo_list->p_ant == NULL) {
        free_undo_node(undo_list);
        undo_list = NULL;
    } else {
        struct undo * ul = undo_list->p_sig; // Previous
        free_undo_node(ul);
        undo_list->p_sig = NULL;
    }

    return;
}

// Remove undolist content
/**
 * \brief Remove undolist content
 *
 * \return none
 */

void clear_undo_list() {
    if (undo_list == NULL) return;

    // Go to the beginning of the list
    while (undo_list->p_ant != NULL ) {
        undo_list = undo_list->p_ant;
    }

    struct undo * ul = undo_list;

    free_undo_node(ul);

    undo_list = NULL;
    undo_list_pos = 0;

    return;
}

/**
 * \brief Returnt the length of the undo list
 *
 * \return length of undolist
 */

int len_undo_list() {
    return undo_list_len;
}

/**
 * \brief TODO Document copy_to_undostruct()
 *
 * \details Take a range of 'ent' elements and create new ones (as many
 * as there are elements inside the specified range). Then copy the content
 * of the original ones to the new ones and save them into the 'added' or
 * 'removed' list of undo_items, according to the char type.
 *
 * Example usage:
 * @code
 *     <function name>();
 * @endcode
 * returns: none
 */

void copy_to_undostruct (int row_desde, int col_desde, int row_hasta, int col_hasta, char type) {
    int c, r;
    struct ent * p;
    int repeated;

    for (r = row_desde; r <= row_hasta; r++)
        for (c = col_desde; c <= col_hasta; c++) {
            p = *ATBL(tbl, r, c);
            if (p == NULL) continue;

            // here check that ent to add is not already in the list
            // if so, avoid to add a duplicate ent
            struct ent * lista = type == 'a' ? undo_item.added : undo_item.removed;
            repeated = 0;
            while (lista != NULL) {
                if (lista->row == r && lista->col == c) {
                    repeated = 1;
                    break;
                }
                lista = lista->next;
            }
            if (repeated) continue;

            // not repeated - we malloc an ent and add it to list
            struct ent * e = (struct ent *) malloc( (unsigned) sizeof(struct ent) );
            cleanent(e);
            //copyent(e, lookat(r, c), 0, 0, 0, 0, 0, 0, 0);
            copyent(e, lookat(r, c), 0, 0, 0, 0, 0, 0, 'u');

            // Append 'ent' element at the beginning
            if (type == 'a') {
                e->next = undo_item.added;
                undo_item.added = e;
            } else {
                e->next = undo_item.removed;
                undo_item.removed = e;
            }

        }
    return;
}

/**
 * \brief TODO Codument add_undo_aux_ent()
 *
 * \details This function is used to keep aux ents in the undo struct.
 * This is used for undoing dr, dc, sk, and sh commands. It stores a copy
 * of the ent received as a parameter and returns the pointer to that copy.
 *
 * \return pointer to a copied ent
 */

struct ent * add_undo_aux_ent(struct ent * e) {
    struct ent * e_new = (struct ent *) malloc( (unsigned) sizeof(struct ent) );
    cleanent(e_new);
    copyent(e_new, e, 0, 0, 0, 0, 0, 0, 'u'); // copy ent with special='u' (undo)
    e_new->next = undo_item.aux_ents;         // add e_new to beginning of list
    undo_item.aux_ents = e_new;
    return e_new;
}

/**
 * \brief TODO Document add_undo_col_format()
 *
 * \param[in] col
 * \param[in] type
 * \param[in] fwidth
 * \param[in] precision
 * \param[in] realfmt
 *
 * \return none
 */

void add_undo_col_format(int col, int type, int fwidth, int precision, int realfmt) {
    if (undo_item.cols_format == NULL) {
        undo_item.cols_format = (struct undo_cols_format *) malloc( sizeof(struct undo_cols_format));
        undo_item.cols_format->length = 1;
        undo_item.cols_format->cols = (struct undo_col_info *) malloc( sizeof(struct undo_col_info));
    } else {
        undo_item.cols_format->length++;
        undo_item.cols_format->cols = (struct undo_col_info *) realloc(undo_item.cols_format->cols, undo_item.cols_format->length * sizeof(struct undo_col_info));
    }
    undo_item.cols_format->cols[ undo_item.cols_format->length - 1].type = type;
    undo_item.cols_format->cols[ undo_item.cols_format->length - 1].col = col;
    undo_item.cols_format->cols[ undo_item.cols_format->length - 1].fwidth = fwidth;
    undo_item.cols_format->cols[ undo_item.cols_format->length - 1].precision = precision;
    undo_item.cols_format->cols[ undo_item.cols_format->length - 1].realfmt = realfmt;
    return;
}

/**
 * \brief TODO Document save_undo_range_shift()
 *
 * \detials Take a range, a rows and columns delta and save them into
 * they undo struct. Used to shift ranges when UNDO or REDO without
 * duplicating 'ent'elements.
 *
 * \param[in] delta_rows
 * \param[in] delta_cols
 * \param[in] tlrow
 * \param[in] tlcol
 * \param[in] brrow
 * \param[in] brcol
 *
 * \return none
 */

void save_undo_range_shift(int delta_rows, int delta_cols, int tlrow, int tlcol, int brrow, int brcol) {
    struct undo_range_shift * urs = (struct undo_range_shift *) malloc( (unsigned) sizeof(struct undo_range_shift ) );
    urs->delta_rows = delta_rows;
    urs->delta_cols = delta_cols;
    urs->tlrow = tlrow;
    urs->tlcol = tlcol;
    urs->brrow = brrow;
    urs->brcol = brcol;
    undo_item.range_shift = urs;
    return;
}

/*
 * This function is used for undoing and redoing
 * changes caused by commands that hide/show rows/columns of screen
 * such as Zr Zc Sc Sr commands.
 * it stores in four different lists (int * list) the row or columns numbers
 * that are showed or hidden because of a change.
 * As these lists are dynamically built, in the first position of every list,
 * we always store the number of elements that the list has.
 */
/**
 * \brief TODO Document undo_hide_show()
 *
 * \details This function is used for undoint and redoing changes
 * caused by commands that hide/show rows/columns of screen such
 * as Zr, Zc, Sc, and Sr commands.
 *
 * It stores in four different lists (int * list) the row or column numbers
 * that are shown or hidden because of a change. As these lists are
 * dynamically built, in the first position of every list, we always store
 * the number of elements that the list has.
 *
 * \param[in] row
 * \param[in] col
 * \param[in] type
 * \param[in] arg
 *
 * \return none
 */

void undo_hide_show(int row, int col, char type, int arg) {
    int i;
    if (type == 'h') {
        if (row > -1) {        // hide row
            if (undo_item.row_hidded == NULL) {
                undo_item.row_hidded = (int *) malloc(sizeof(int) * (arg + 1));
                undo_item.row_hidded[0] = 0;
            } else
                undo_item.row_hidded = (int *) realloc(undo_item.row_hidded, sizeof(int) * (undo_item.row_hidded[0] + arg + 1));

            for (i=0; i < arg; i++)
                undo_item.row_hidded[undo_item.row_hidded[0] + i + 1] = row + i;

            undo_item.row_hidded[0] += arg; // keep in first position the number of elements (rows)

        } else if (col > -1) { // hide col
            if (undo_item.col_hidded == NULL) {
                undo_item.col_hidded = (int *) malloc(sizeof(int) * (arg + 1));
                undo_item.col_hidded[0] = 0;
            } else
                undo_item.col_hidded = (int *) realloc(undo_item.col_hidded, sizeof(int) * (undo_item.col_hidded[0] + arg + 1));

            for (i=0; i < arg; i++)
                undo_item.col_hidded[undo_item.col_hidded[0] + i + 1] = col + i;

            undo_item.col_hidded[0] += arg; // keep in first position the number of elements (cols)
        }

    } else if (type == 's') {
        if (row > -1) {        // show row
            if (undo_item.row_showed == NULL) {
                undo_item.row_showed = (int *) malloc(sizeof(int) * (arg + 1));
                undo_item.row_showed[0] = 0;
            } else
                undo_item.row_showed = (int *) realloc(undo_item.row_showed, sizeof(int) * (undo_item.row_showed[0] + arg + 1));

            for (i=0; i < arg; i++)
                undo_item.row_showed[undo_item.row_showed[0] + i + 1] = row + i;

            undo_item.row_showed[0] += arg; // keep in first position the number of elements (rows)

        } else if (col > -1) { // show col
            if (undo_item.col_showed == NULL) {
                undo_item.col_showed = (int *) malloc(sizeof(int) * (arg + 1));
                undo_item.col_showed[0] = 0;
            } else
                undo_item.col_showed = (int *) realloc(undo_item.col_showed, sizeof(int) * (undo_item.col_showed[0] + arg + 1));

            for (i=0; i < arg; i++)
                undo_item.col_showed[undo_item.col_showed[0] + i + 1] = col + i;

            undo_item.col_showed[0] += arg; // keep in first position the number of elements (cols)

        }
    }
    return;
}

/**
 * \brief Do UNDO operation
 *
 * Shift a range of an undo shift range to the original position, if any,
 * append 'ent' elements 'removed' and remove those from 'added'.
 *
 * \return none
 */

void do_undo() {
    if (undo_list == NULL || undo_list_pos == 0) {
        sc_error("No UNDO's left");
        return;
    }
    //sc_info("%d %d", undo_list_pos, len_undo_list());

    int ori_currow = currow;
    int ori_curcol = curcol;
    int mf = modflg; // save modflag status

    struct undo * ul = undo_list;

    // removed added ents
    struct ent * i = ul->added;
    while (i != NULL) {
        struct ent * pp = *ATBL(tbl, i->row, i->col);
        clearent(pp);
        cleanent(pp);
        i = i->next;
    }

    // Make undo shift, if any
    if (ul->range_shift != NULL) {
        // fix marks
        if (ul->range_shift->delta_rows > 0)      // sj
            fix_marks(-(ul->range_shift->brrow - ul->range_shift->tlrow + 1), 0, ul->range_shift->tlrow, maxrow, ul->range_shift->tlcol, ul->range_shift->brcol);
        else if (ul->range_shift->delta_rows < 0) // sk
            fix_marks( (ul->range_shift->brrow - ul->range_shift->tlrow + 1), 0, ul->range_shift->tlrow, maxrow, ul->range_shift->tlcol, ul->range_shift->brcol);
        if (ul->range_shift->delta_cols > 0)      // sl
            fix_marks(0, -(ul->range_shift->brcol - ul->range_shift->tlcol + 1), ul->range_shift->tlrow, ul->range_shift->brrow, ul->range_shift->tlcol, maxcol);
        else if (ul->range_shift->delta_cols < 0) // sh
            fix_marks(0,  (ul->range_shift->brcol - ul->range_shift->tlcol + 1), ul->range_shift->tlrow, ul->range_shift->brrow, ul->range_shift->tlcol, maxcol);

        shift_range(- ul->range_shift->delta_rows, - ul->range_shift->delta_cols,
            ul->range_shift->tlrow, ul->range_shift->tlcol, ul->range_shift->brrow, ul->range_shift->brcol);

        // shift col_formats here
        if (ul->range_shift->tlcol >= 0 && ul->range_shift->tlrow == 0 && ul->range_shift->brrow == maxrow) { // && ul->range_shift->delta_cols > 0) {
            int i;
            if (ul->range_shift->delta_cols > 0)
            for (i = ul->range_shift->brcol + ul->range_shift->delta_cols; i <= maxcol; i++) {
                fwidth[i - ul->range_shift->delta_cols] = fwidth[i];
                precision[i - ul->range_shift->delta_cols] = precision[i];
                realfmt[i - ul->range_shift->delta_cols] = realfmt[i];
            }
            else
            for (i = maxcol; i >= ul->range_shift->tlcol - ul->range_shift->delta_cols; i--) {
                 fwidth[i] = fwidth[i + ul->range_shift->delta_cols];
                 precision[i] = precision[i + ul->range_shift->delta_cols];
                 realfmt[i] = realfmt[i + ul->range_shift->delta_cols];
            }
        }
    }

    //update(TRUE); //FIXME remove this line. its just to help debugging

    // Change cursor position
    //if (ul->removed != NULL) {
    //    currow = ul->removed->row;
    //    curcol = ul->removed->col;
    //}

    // Append 'ent' elements from the removed ones
    struct ent * j = ul->removed;
    while (j != NULL) {
        struct ent * h;
        if ((h = *ATBL(tbl, j->row, j->col))) clearent(h);
        struct ent * e_now = lookat(j->row, j->col);
        (void) copyent(e_now, j, 0, 0, 0, 0, 0, 0, 0);
        j = j->next;
    }

    // Show hidden cols and rows
    // Hide visible cols and rows
    if (ul->col_hidded != NULL) {
        int * pd = ul->col_hidded;
        int left = *(pd++);
        while (left--) {
            col_hidden[*(pd++)] = FALSE;
        }
    }
    else if (ul->col_showed  != NULL) {
        int * pd = ul->col_showed;
        int left = *(pd++);
        while (left--) {
            col_hidden[*(pd++)] = TRUE;
        }
    }
    else if (ul->row_hidded  != NULL) {
        int * pd = ul->row_hidded;
        int left = *(pd++);
        while (left--) {
            row_hidden[*(pd++)] = FALSE;
        }
    }
    else if (ul->row_showed  != NULL) {
        int * pd = ul->row_showed;
        int left = *(pd++);
        while (left--) {
            row_hidden[*(pd++)] = TRUE;
        }
    }

    // Restore previous col format
    if (ul->cols_format != NULL) {
        struct undo_cols_format * uf = ul->cols_format;
        int size = uf->length;
        int i;

        for (i=0; i < size; i++) {
           if (uf->cols[i].type == 'R') {
               fwidth[uf->cols[i].col]    = uf->cols[i].fwidth;
               precision[uf->cols[i].col] = uf->cols[i].precision;
               realfmt[uf->cols[i].col]   = uf->cols[i].realfmt;
           }
        }
    }

    // for every ent in added and removed, we reeval expression to update graph
    struct ent * ie = ul->added;
    while (ie != NULL) {
        struct ent * p;
        if ((p = *ATBL(tbl, ie->row, ie->col)) && p->expr)
            EvalJustOneVertex(p, ie->row, ie->col, 1);
        ie = ie->next;
    }
    ie = ul->removed;
    while (ie != NULL) {
        struct ent * p;
        if ((p = *ATBL(tbl, ie->row, ie->col)) && p->expr)
            EvalJustOneVertex(p, ie->row, ie->col, 1);
        ie = ie->next;
    }

    // Restores cursor position
    currow = ori_currow;
    curcol = ori_curcol;

    // decrease modflg
    modflg= mf - 1;

    if (undo_list->p_ant != NULL) undo_list = undo_list->p_ant;
    undo_list_pos--;
    sc_info("Change: %d of %d", undo_list_pos, len_undo_list());
    return;
}

/**
 * \brief Do REDO
 *
 * Shift a range of an undo shift range to the original position, if any,
 * append 'ent' elements from 'added' and remove those from 'removed'.
 *
 * \return none
 */

void do_redo() {
    //if ( undo_list == NULL || undo_list_pos == len_undo_list()  ) {
    //FIXME check why undo_list_pos can sometimes be > len_undo_list(). it shouldnt!!
    if ( undo_list == NULL || undo_list_pos >= len_undo_list()  ) {
        sc_error("No REDO's left");
        return;
    }
    //sc_info("%d %d", undo_list_pos, len_undo_list());

    int ori_currow = currow;
    int ori_curcol = curcol;
    int mf = modflg; // save modflag status

    if (undo_list->p_ant == NULL && undo_list_pos == 0);
    else if (undo_list->p_sig != NULL) undo_list = undo_list->p_sig;

    struct undo * ul = undo_list;

    // Remove 'ent' elements
    struct ent * i = ul->removed;
    while (i != NULL) {
        struct ent * pp = *ATBL(tbl, i->row, i->col);
        clearent(pp);
        cleanent(pp);
        i = i->next;
    }

    // Make undo shift, if any
    if (ul->range_shift != NULL) {
        // fix marks
        if (ul->range_shift->delta_rows > 0)      // sj
            fix_marks( (ul->range_shift->brrow - ul->range_shift->tlrow + 1), 0, ul->range_shift->tlrow, maxrow, ul->range_shift->tlcol, ul->range_shift->brcol);
        else if (ul->range_shift->delta_rows < 0) // sk
            fix_marks(-(ul->range_shift->brrow - ul->range_shift->tlrow + 1), 0, ul->range_shift->tlrow, maxrow, ul->range_shift->tlcol, ul->range_shift->brcol);
        if (ul->range_shift->delta_cols > 0)      // sl
            fix_marks(0,  (ul->range_shift->brcol - ul->range_shift->tlcol + 1), ul->range_shift->tlrow, ul->range_shift->brrow, ul->range_shift->tlcol, maxcol);
        else if (ul->range_shift->delta_cols < 0) // sh
            fix_marks(0, -(ul->range_shift->brcol - ul->range_shift->tlcol + 1), ul->range_shift->tlrow, ul->range_shift->brrow, ul->range_shift->tlcol, maxcol);

        shift_range(ul->range_shift->delta_rows, ul->range_shift->delta_cols,
            ul->range_shift->tlrow, ul->range_shift->tlcol, ul->range_shift->brrow, ul->range_shift->brcol);

        // shift col_formats here
        if (ul->range_shift->tlcol >= 0 && ul->range_shift->tlrow == 0 && ul->range_shift->brrow == maxrow) {
            int i;
            if (ul->range_shift->delta_cols > 0)
            for (i = maxcol; i >= ul->range_shift->tlcol + ul->range_shift->delta_cols; i--) {
                fwidth[i] = fwidth[i - ul->range_shift->delta_cols];
                precision[i] = precision[i - ul->range_shift->delta_cols];
                realfmt[i] = realfmt[i - ul->range_shift->delta_cols];
            }
            else
            for (i = ul->range_shift->tlcol; i - ul->range_shift->delta_cols <= maxcol; i++) {
                fwidth[i] = fwidth[i - ul->range_shift->delta_cols];
                precision[i] = precision[i - ul->range_shift->delta_cols];
                realfmt[i] = realfmt[i - ul->range_shift->delta_cols];
            }
        }
    }

    //update(TRUE);

    // Change cursor position
    //if (ul->p_sig != NULL && ul->p_sig->removed != NULL) {
    //    currow = ul->p_sig->removed->row;
    //    curcol = ul->p_sig->removed->col;
    //}

    // Append 'ent' elements
    struct ent * j = ul->added;
    while (j != NULL) {
        struct ent * h;
        if ((h = *ATBL(tbl, j->row, j->col))) clearent(h);
        struct ent * e_now = lookat(j->row, j->col);
        (void) copyent(e_now, j, 0, 0, 0, 0, 0, 0, 0);
        j = j->next;
    }

    // Hide previously hidden cols and rows
    // Show previously visible cols and rows
    if (ul->col_hidded != NULL) {
        int * pd = ul->col_hidded;
        int left = *(pd++);
        while (left--) {
            col_hidden[*(pd++)] = TRUE;
        }
    }
    else if (ul->col_showed  != NULL) {
        int * pd = ul->col_showed;
        int left = *(pd++);
        while (left--) {
            col_hidden[*(pd++)] = FALSE;
        }
    }
    else if (ul->row_hidded  != NULL) {
        int * pd = ul->row_hidded;
        int left = *(pd++);
        while (left--) {
            row_hidden[*(pd++)] = TRUE;
        }
    }
    else if (ul->row_showed  != NULL) {
        int * pd = ul->row_showed;
        int left = *(pd++);
        while (left--) {
            row_hidden[*(pd++)] = FALSE;
        }
    }

    // Restore new col format
    if (ul->cols_format != NULL) {
        struct undo_cols_format * uf = ul->cols_format;
        int size = uf->length;
        int i;

        for (i=0; i < size; i++) {
            if (uf->cols[i].type == 'A') {
                fwidth[uf->cols[i].col]    = uf->cols[i].fwidth;
                precision[uf->cols[i].col] = uf->cols[i].precision;
                realfmt[uf->cols[i].col]   = uf->cols[i].realfmt;
            }
        }
    }

    // for every ent in added and removed, we reeval expression to update graph
    struct ent * ie = ul->added;
    while (ie != NULL) {
        struct ent * p;
        if ((p = *ATBL(tbl, ie->row, ie->col)) && p->expr)
            EvalJustOneVertex(p, ie->row, ie->col, 1);
        ie = ie->next;
    }
    ie = ul->removed;
    while (ie != NULL) {
        struct ent * p;
        if ((p = *ATBL(tbl, ie->row, ie->col)) && p->expr)
            EvalJustOneVertex(p, ie->row, ie->col, 1);
        ie = ie->next;
    }

    // Restores cursor position
    currow = ori_currow;
    curcol = ori_curcol;

    // increase modflg
    modflg = mf + 1;

    sc_info("Change: %d of %d", undo_list_pos + 1, len_undo_list());
    undo_list_pos++;

    return;
}
#endif
