/*******************************************************************************
 * Copyright (c) 2013-2025, Andrés G. 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 G. Martinelli         *
 *    <andmarti@gmail.com>.                                                    *
 * 4. Neither the name of the Andrés G. 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 ANDRÉS G. 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 ANDRÉS G. 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 yank.c
 * \author Andrés G. Martinelli <andmarti@gmail.com>
 * \date 2025-05-05
 * \brief TODO Write a tbrief file description.
 *
 * \details Yanklist doesn't keep references to 'ent' elements, it
 * creates new nodes.The yanklist is constantly cleaned out. Example: When
 * removing 'ent' elements with `dc`, the original ones are removed
 * and new elements are created in the yanklist. Important: each 'ent'
 * element should keep a row and col.
 */

#include "sc.h"
#include "stdlib.h"
#include "marks.h"
#include "macros.h"
#include "color.h"
#include "cmds/cmds.h"
#include "conf.h"
#include "yank.h"
#include "graph.h"
#include "xmalloc.h" // for scxfree

#ifdef UNDO
#include "undo.h"
#endif

extern struct ent * forw_row(struct sheet * sh, int arg);
extern struct ent * back_row(struct sheet * sh, int arg);
extern struct ent * forw_col(struct sheet * sh, int arg);
extern struct ent * back_col(struct sheet * sh, int arg);
extern struct session * session;

int yank_arg;                 // number of rows and columns yanked. Used for commands like `4yr`
char type_of_yank;            // yank type. c=col, r=row, a=range, e=cell, '\0'=no yanking
static struct ent_ptr * yanklist;
struct ent_ptr * yanklist_tail;   // so we can always add ents at the end of the list easily
unsigned int yanked_cells = 0;// keeping this helps performance


/**
 * \brief init_yanklist()
 * \return none
 */
void init_yanklist() {
    yanked_cells = 0;
    type_of_yank = YANK_NULL;
    yanklist = NULL;
    yanklist_tail = NULL;
}


/**
 * \brief Return the yanklist
 * \return struct ent *
 */
struct ent_ptr * get_yanklist() {
    return yanklist;
}


/**
 * \brief Remove elements from the yanklist and free its memory
 * \return none
 */
void free_yanklist () {
    if (yanklist == NULL) return;

    // free each ent content
    struct ent_ptr * r = yanklist;
    struct ent_ptr * e;
    while (r != NULL) {
        e = r->next;
        clearent(r->vp);
        r = e;
    }
    free(yanklist->vp); // free ents
    yanklist->vp = NULL;

    // free yanklist ent_ptr
    free(yanklist);
    yanked_cells = 0;
    yanklist = NULL;
    yanklist_tail = NULL;
    return;
}

/**
 * \brief Add an already alloc'ed 'ent_ptr' element to the yanklist
 * yanklist_tail is pointer to the last ent in the list
 * \param[in] item 'ent' element to add to the yanklist
 * \return none
 */
void add_ent_to_yanklist(struct ent_ptr * item) {
    yanked_cells++;

   // If yanklist is empty, insert at the beginning
    if (yanklist == NULL) {
        yanklist = item;
        yanklist_tail = item;
        return;
    }

    // If yanklist is NOT empty, insert it at the end
    yanklist_tail->next = item;
    yanklist_tail = item;
    return;
}


/**
 * \brief Yank a range of ents of a given range of a sheet
 * \param[in] struct sheet * sh
 * \param[in] tlrow
 * \param[in] tlcol
 * \param[in] brrow
 * \param[in] brcol
 * \param[in] type yank type. c=col, r=row, a=range, e=cell. '\0'=no yanking,
 * 's' sort. Used for pasting.
 * \param[in] arg number of rows or columns yanked. Used in commands like
 * '4yr'. Used for pasting.
 * \return none
 */
void yank_area(struct sheet * sh, int tlrow, int tlcol, int brrow, int brcol, char type, int arg) {
    int r,c;
    type_of_yank = type;
    yank_arg = arg;
    free_yanklist();
    int ignore_hidden = get_conf_int("ignore_hidden");

    struct ent * e_ori;
    // ask for memory to keep struct ent_ptr * and struct ent * for the whole range
    struct ent_ptr * y_cells = (struct ent_ptr *) calloc((brrow-tlrow+1)*(brcol-tlcol+1), sizeof(struct ent_ptr));
    struct ent * y_cells_vp = (struct ent *) calloc((brrow-tlrow+1)*(brcol-tlcol+1), sizeof(struct ent));


    // work on issue 674
    // if ignore_hidden is set we need to keep the number of hidden rows in case
    long hid = 0;

    for (r = tlrow; r <= brrow; r++) {
        if (sh->row_hidden[r]) { hid++; continue; }
        for (c = tlcol; c <= brcol; c++) {
            e_ori = *ATBL(sh, sh->tbl, r, c);

            if (e_ori == NULL) continue;

            // initialize the 'ent'
            y_cells->vp = y_cells_vp++;
            cleanent(y_cells->vp);

            // Copy 'e_ori' contents to 'y_cells' ent
            y_cells->sheet = sh;
            copyent(y_cells->vp, sh, e_ori, 0, 0, 0, 0, 0, 0, 0);

            // Important: each 'ent' element keeps the corresponding row and col
            (y_cells)->vp->row = e_ori->row;
            (y_cells)->vp->col = e_ori->col;

            // work on issue 674
            // if ignore_hidden is set we subtract that number to the internal row so all the pasted rows are
            // adjacent. this is a special case when we're pulling hidden rows and we want to ignore those hidden ranges
            // (example if yanking a result of a filter)
            if (ignore_hidden) (y_cells)->vp->row -= hid;

            add_ent_to_yanklist(y_cells++);
        }
    }
    // this takes care of a potential memory leak if no ent was added to yanklist
    // for instance when deleting empty row
    if (! yanked_cells) {
        free(y_cells);
        free(y_cells_vp);
    }
    return;
}


/**
 * \brief paste_yanked_ents()
 *
 * \details This function is used for pasting ents that were yanked
 * with yr, yc, dr, or dc. It is also used for sorting.
 * If above == 1, paste is done above current row or the
 * right of the current column. Ents that were yanked using yy or yanked
 * ents of a range, are always pasted over currow and curcol positions
 * of the given sheet.
 * diffr: difference between current rows and the yanked 'ent'
 * diffc: difference between current columns and the yanked 'ent'
 * When sorting, rwo and col values can vary from yank to paste
 * time, so diffr should be zero.
 * When implementing column sorting, diffc should be zero as well!
 * type indicates if pasting format only, valuue only for the
 * whole content.
 * type_paste: c=col, r=row, a=range, e=cell, '\0'=no yanking, t=transpose
 *
 * \param[in] struct sheet * sh
 * \param[in] above
 * \param[in] type_paste
 *
 * \return -1 if locked cells are found
 * \return 0 otherwise
 */
int paste_yanked_ents(struct sheet * sh, int above, int type_paste) {
    struct roman * roman = session->cur_doc;
    if (yanklist == NULL) return 0;

    struct ent_ptr * yl = yanklist;
    struct ent_ptr * yll = yl;
    int diffr = 0, diffc = 0 , ignorelock = 0;

    extern struct ent_ptr * deps;
    if (yl->sheet == NULL) yl->sheet = roman->cur_sh;

#ifdef UNDO
    create_undo_action();
#endif

    if (type_of_yank == YANK_SORT) {                   // paste a range that was yanked in the sort function
        diffr = 0;
        diffc = sh->curcol - yl->vp->col;
        ignorelock = 1;

    } else if (type_of_yank == YANK_RANGE || type_of_yank == YANK_CELL) { // paste cell or range
        diffr = sh->currow - yl->vp->row;
        diffc = sh->curcol - yl->vp->col;

    } else if (type_of_yank == YANK_ROW && sh == yl->sheet) {             // paste row
        int c = yank_arg;
#ifdef UNDO
        copy_to_undostruct(sh, sh->currow + ! above, 0, sh->currow + ! above - 1 + yank_arg, sh->maxcol, UNDO_DEL, IGNORE_DEPS, NULL);
#endif
        while (c--) above ? insert_row(sh, 0) : insert_row(sh, 1);
        if (! above) sh->currow = forw_row(sh, 1)->row;                   // paste below
        diffr = sh->currow - yl->vp->row;
        diffc = yl->vp->col;
        fix_marks(sh, yank_arg, 0, sh->currow, sh->maxrow, 0, sh->maxcol);
#ifdef UNDO
        save_undo_range_shift(yank_arg, 0, sh->currow, 0, sh->currow - 1 + yank_arg, sh->maxcol);
#endif

    } else if (type_of_yank == YANK_COL) {             // paste col
        int c = yank_arg;
#ifdef UNDO
        copy_to_undostruct(sh, 0, sh->curcol + above, sh->maxrow, sh->curcol + above - 1 + yank_arg, UNDO_DEL, IGNORE_DEPS, NULL);
#endif
        while (c--) above ? insert_col(sh, 1) : insert_col(sh, 0);        // insert cols to the right if above or to the left
        diffr = yl->vp->row;
        diffc = sh->curcol - yl->vp->col;
        fix_marks(sh, 0, yank_arg, 0, sh->maxrow, sh->curcol, sh->maxcol);
#ifdef UNDO
        save_undo_range_shift(0, yank_arg, 0, sh->curcol, sh->maxrow, sh->curcol - 1 + yank_arg);
#endif
    }

    // paste cell or range
    if (type_of_yank == YANK_RANGE || type_of_yank == YANK_CELL) {
        // first check if there are any locked cells over destination
        // if so, just return
        int r, c;
        while (yll != NULL) {
            if (type_paste == YANK_TRANSPOSE) {
                diffr = sh->currow - yl->vp->col;
                diffc = sh->curcol - yl->vp->row;
                r = yll->vp->col + diffr;
                c = yll->vp->row + diffc;
            } else {
                r = yll->vp->row + diffr;
                c = yll->vp->col + diffc;
            }
            checkbounds(sh, &r, &c);
            if ((type_paste == YANK_TRANSPOSE && any_locked_cells(sh, yll->vp->col + diffr, yll->vp->row + diffc, yll->vp->col + diffr, yll->vp->row + diffc)) 
            || any_locked_cells(sh, yll->vp->row + diffr, yll->vp->col + diffc, yll->vp->row + diffr, yll->vp->col + diffc)) {
#ifdef UNDO
                dismiss_undo_item(NULL);
#endif
                return -1;
            }
            yll = yll->next;
        }
    }
    // its really needed to save deps of yanked ents?
    ents_that_depends_on_list(yl, diffr, diffc);

#ifdef UNDO
    // ask for memory to save the entire yanklist (and its dependencies) in the undo struct
    // note that the yanked ents could share dependencies.
    struct ent_ptr * y_cells = (struct ent_ptr *) calloc(2*(yanked_cells + (deps != NULL ? deps->vf : 0)), sizeof(struct ent_ptr));
    save_yl_pointer_after_calloc(y_cells);

    // save in undo the dependent ents before the paste
    copy_to_undostruct(sh, 0, 0, -1, -1, UNDO_DEL, HANDLE_DEPS, &y_cells);
#endif

    // paste each ent in yank list
    while (yl != NULL) {
        if (yl->sheet == NULL) yl->sheet = roman->cur_sh;

#ifdef UNDO
        if (type_paste == YANK_TRANSPOSE)
            copy_cell_to_undostruct(y_cells++, sh, lookat(sh, yl->vp->col + diffr, yl->vp->row + diffc), UNDO_DEL);
        else
            copy_cell_to_undostruct(y_cells++, sh, lookat(sh, yl->vp->row + diffr, yl->vp->col + diffc), UNDO_DEL);
#endif

        // here we delete current content of "destino" ent.
        if (type_paste == YANK_RANGE || type_paste == YANK_SORT) {
            erase_area(sh, yl->vp->row + diffr, yl->vp->col + diffc, yl->vp->row + diffr, yl->vp->col + diffc, ignorelock, 0);
        } else if (type_paste == YANK_TRANSPOSE) {
            erase_area(sh, yl->vp->col + diffc, yl->vp->row + diffr, yl->vp->col + diffc, yl->vp->row + diffr, ignorelock, 0);
        }

        struct ent * destino;
        if (type_paste == YANK_TRANSPOSE) {
            destino = lookat(sh, yl->vp->col + diffr, yl->vp->row + diffc);
        } else {
            destino = lookat(sh, yl->vp->row + diffr, yl->vp->col + diffc);
        }

        if (type_paste == YANK_RANGE || type_paste == YANK_SORT) {
            (void) copyent(destino, sh, yl->vp, 0, 0, 0, 0, 0, 0, 0);
        } else if (type_paste == YANK_FORMAT) {
            (void) copyent(destino, sh, yl->vp, 0, 0, 0, 0, 0, 0, 'f');
        } else if (type_paste == YANK_VALUE) {
            (void) copyent(destino, sh, yl->vp, 0, 0, 0, 0, 0, 0, 'v');
            if (yl->sheet == sh && yl->vp->row == destino->row && yl->vp->col == destino->col) {
                efree(destino->expr);
                destino->expr = NULL;
            }
        } else if (type_paste == YANK_REF) {
            (void) copyent(destino, sh, yl->vp, diffr, diffc, 0, 0, sh->maxrows, sh->maxcols, 'c');
        } else if (type_paste == YANK_TRANSPOSE) {
            (void) copyent(destino, sh, yl->vp, 0, 0, 0, 0, 0, 0, 0);
        }
        if (type_paste == YANK_TRANSPOSE) {
            destino->row += diffc;
            destino->col += diffr;
        } else {
            destino->row += diffr;
            destino->col += diffc;
        }

        /******************** this might be put outside the loop  */
        // if so, use EvalRange
        // sync and then eval.
        //sync_refs(sh);

        if (destino->expr) {
            syncref(sh, destino->expr);
            if (get_conf_int("autocalc")) EvalJustOneVertex(sh, destino, 1);
            //EvalRange(destino->row, destino->col, destino->row, destino->col);
        }

        int i;
        for (i = 0; deps != NULL && i < deps->vf; i++) {
            syncref(sh, deps[i].vp->expr);
            if (get_conf_int("autocalc") && deps[i].vp->expr) EvalJustOneVertex(sh, deps[i].vp, 0);
        }
        /*******************/
#ifdef UNDO
        if (type_paste == YANK_TRANSPOSE)
            copy_cell_to_undostruct(y_cells++, sh, lookat(sh, yl->vp->col + diffr, yl->vp->row + diffc), UNDO_ADD);
        else
            copy_cell_to_undostruct(y_cells++, sh, lookat(sh, yl->vp->row + diffr, yl->vp->col + diffc), UNDO_ADD);
#endif
        yl = yl->next;
    }

#ifdef UNDO
    // save in undo the dependent ents after the paste
    copy_to_undostruct(sh, 0, 0, -1, -1, UNDO_ADD, HANDLE_DEPS, &y_cells);
#endif

    //rebuild_graph();
    //if (get_conf_int("autocalc")) EvalAll();
    roman->modflg++;

#ifdef UNDO
    end_undo_action();
    if (deps != NULL) free(deps);
    deps = NULL;
#endif
    return 0;
}
