/*
 * mtsedit/hist.c
 *
 * Copyright (C) 2019 bzt (bztsrc@gitlab)
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * @brief History (undo / redo) functions
 *
 */

#include "main.h"

#define HISTORYMAX 256

int histtop = 0, histmax = 0, histcapacity = 0;
histlist_t history[HISTORYMAX];
histlist_t histcurr;

/**
 * Prepare for a history transaction
 */
void hist_prepare(int type, int volume)
{
    int i, x, y, z;

    if(histcurr.type != HIST_EMPTY) hist_commit();
    if(type > HIST_NODE)
        mts_getbounds(0, NULL, NULL);
    switch(type) {
        case HIST_NODE:
        case HIST_ROTCW:
        case HIST_ROTCCW:
        case HIST_FLIP:
            volume = 0; break;
        case HIST_BRUSH:
            if(volume < 1) volume = (may - miy + 1) * (maz - miz + 1) * (max - mix + 1);
            histcurr.data.multiple.entry = (hist_t*)malloc(volume * sizeof(hist_t));
            if(!histcurr.data.multiple.entry) error(lang[ERR_MEM]);
        break;
        case HIST_ADDY:
        case HIST_DELY:
            histcurr.y = volume;
            histcurr.x = mix;
            histcurr.z = miz;
            histcurr.data.bulk.pitch = max - mix + 1;
            histcurr.data.bulk.numentry = volume = (maz - miz + 1) * (max - mix + 1);
            histcurr.data.bulk.entry = (node_t*)malloc(volume * sizeof(hist_t));
            if(!histcurr.data.bulk.entry) error(lang[ERR_MEM]);
            if(type == HIST_DELY) {
                for(i = 0, z = miz; z <= maz; z++)
                    for(x = mix; x <= max; x++, i++)
                        memcpy(&histcurr.data.bulk.entry[i], &nodes[currlayer][z][x], sizeof(node_t));
            }
        break;
        case HIST_ADDZ:
        case HIST_DELZ:
            histcurr.z = volume;
            histcurr.x = mix;
            histcurr.y = miy;
            histcurr.data.bulk.pitch = max - mix + 1;
            histcurr.data.bulk.numentry = volume = (may - miy + 1) * (max - mix + 1);
            histcurr.data.bulk.entry = (node_t*)malloc(volume * sizeof(hist_t));
            if(!histcurr.data.bulk.entry) error(lang[ERR_MEM]);
            if(type == HIST_DELZ) {
                for(i = 0, y = miy; y <= may; y++)
                    for(x = mix; x <= max; x++, i++)
                        memcpy(&histcurr.data.bulk.entry[i], &nodes[y][cz][x], sizeof(node_t));
            }
        break;
        case HIST_ADDX:
        case HIST_DELX:
            histcurr.x = volume;
            histcurr.y = miy;
            histcurr.z = miz;
            histcurr.data.bulk.pitch = maz - miz + 1;
            histcurr.data.bulk.numentry = volume = (may - miy + 1) * (maz - miz + 1);
            histcurr.data.bulk.entry = (node_t*)malloc(volume * sizeof(hist_t));
            if(!histcurr.data.bulk.entry) error(lang[ERR_MEM]);
            if(type == HIST_DELX) {
                for(i = 0, y = miy; y <= may; y++)
                    for(z = miz; z <= maz; z++, i++)
                        memcpy(&histcurr.data.bulk.entry[i], &nodes[y][z][cx], sizeof(node_t));
            }
        break;
    }
    histcurr.type = type;
    histcapacity = volume;
}

/**
 * Commit a history transaction
 */
void hist_commit()
{
    int i, j, x, y, z, op0, op2, np0, np2;

    if(histcurr.type > HIST_BRUSH && !histcurr.data.bulk.numentry) {
        if(histcurr.data.bulk.entry) free(histcurr.data.bulk.entry);
        memset(&histcurr, 0, sizeof(histlist_t));
        histcapacity = 0;
        return;
    }
    if(histcurr.type == HIST_BRUSH) {
        if(!histcurr.data.multiple.numentry) {
            if(histcurr.data.multiple.entry) free(histcurr.data.multiple.entry);
            memset(&histcurr, 0, sizeof(histlist_t));
            histcapacity = 0;
            return;
        }
        /* if there's only one node in the list, convert it to single node to save memory */
        if(histcurr.data.multiple.numentry == 1) {
            histcurr.x = histcurr.data.multiple.entry[0].x;
            histcurr.y = histcurr.data.multiple.entry[0].y;
            histcurr.z = histcurr.data.multiple.entry[0].z;
            op0 = histcurr.data.multiple.entry[0].oldparam0;
            op2 = histcurr.data.multiple.entry[0].oldparam2;
            np0 = histcurr.data.multiple.entry[0].newparam0;
            np2 = histcurr.data.multiple.entry[0].newparam2;
            free(histcurr.data.multiple.entry);
            histcurr.data.node.oldparam0 = op0;
            histcurr.data.node.oldparam2 = op2;
            histcurr.data.node.newparam0 = np0;
            histcurr.data.node.newparam2 = np2;
        } else
        if(histcurr.data.multiple.numentry != histcapacity) {
            histcurr.data.multiple.entry = (hist_t*)realloc(histcurr.data.multiple.entry,
                histcurr.data.multiple.numentry * sizeof(hist_t));
            if(!histcurr.data.multiple.entry) error(lang[ERR_MEM]);
        }
    }
    if(histcurr.type == HIST_EMPTY || (histcurr.type == HIST_NODE &&
        histcurr.data.node.oldparam0 == histcurr.data.node.newparam0 &&
        histcurr.data.node.oldparam2 == histcurr.data.node.newparam2)) {
            memset(&histcurr, 0, sizeof(histlist_t));
            return;
        }
    if(histcurr.type == HIST_NODE && histcurr.type == HIST_NODE && histcurr.x == history[histtop].x &&
        histcurr.y == history[histtop].y && histcurr.z == history[histtop].z &&
        histcurr.data.node.oldparam0 == history[histtop].data.node.oldparam0 &&
        histcurr.data.node.newparam0 == history[histtop].data.node.oldparam0 &&
        histcurr.data.node.newparam0 == history[histtop].data.node.newparam0) {
            history[histtop].data.node.newparam2 = histcurr.data.node.newparam2;
            memset(&histcurr, 0, sizeof(histlist_t));
            return;
        }
    if(histcurr.type == HIST_ADDY) {
        for(i = j = 0, z = miz; z <= maz; z++)
            for(x = mix; x <= max; x++, i++)
                if(nodes[currlayer][z][x].param0) {
                    memcpy(&histcurr.data.bulk.entry[i], &nodes[currlayer][z][x], sizeof(node_t));
                    j++;
                }
        if(!j) {
            free(histcurr.data.bulk.entry);
            histcurr.data.bulk.entry = NULL;
            histcurr.data.bulk.numentry = 0;
        }
    }
    if(histcurr.type == HIST_ADDZ) {
        for(i = j = 0, y = miy; y <= may; y++)
            for(x = mix; x <= max; x++, i++)
                if(nodes[y][cz][x].param0) {
                    memcpy(&histcurr.data.bulk.entry[i], &nodes[y][cz][x], sizeof(node_t));
                    j++;
                }
        if(!j) {
            free(histcurr.data.bulk.entry);
            histcurr.data.bulk.entry = NULL;
            histcurr.data.bulk.numentry = 0;
        }
    }
    if(histcurr.type == HIST_ADDX) {
        for(i = j = 0, y = miy; y <= may; y++)
            for(z = miz; z <= maz; z++, i++)
                if(nodes[y][z][cx].param0) {
                    memcpy(&histcurr.data.bulk.entry[i], &nodes[y][z][cx], sizeof(node_t));
                    j++;
                }
        if(!j) {
            free(histcurr.data.bulk.entry);
            histcurr.data.bulk.entry = NULL;
            histcurr.data.bulk.numentry = 0;
        }
    }
    if(histtop != 0) {
        hist_free(0, histtop);
        memcpy(&history[0], &history[histtop], (histmax - histtop) * sizeof(histlist_t));
        histmax -= histtop;
        if(histmax < 0) histmax = 0;
    }
    hist_free(HISTORYMAX-1, HISTORYMAX);
    if(histmax)
        memmove(&history[1], &history[0], histmax * sizeof(histlist_t));
    if(histmax + 1 < HISTORYMAX) histmax++;
    memcpy(&history[0], &histcurr, sizeof(histlist_t));
    memset(&histcurr, 0, sizeof(histlist_t));
    histcapacity = 0;
    histtop = 0;
}

/**
 * Add to history
 */
void hist_add(int y, int z, int x, unsigned short op0, unsigned char op2, unsigned short np0, unsigned char np2)
{
    int i;

    if(histcurr.type == HIST_NODE) {
        if(op0 == np0 && op2 == np2) histcurr.type = HIST_EMPTY;
        else {
            histcurr.x = x;
            histcurr.y = y;
            histcurr.z = z;
            histcurr.data.node.oldparam0 = op0;
            histcurr.data.node.oldparam2 = op2;
            histcurr.data.node.newparam0 = np0;
            histcurr.data.node.newparam2 = np2;
        }
    } else if(histcurr.type != HIST_EMPTY && (op0 != np0 || op2 != np2)) {
        i = histcurr.data.multiple.numentry++;
        if(histcurr.data.multiple.numentry >= histcapacity) {
            histcapacity += 1024;
            histcurr.data.multiple.entry = (hist_t*)realloc(histcurr.data.multiple.entry, histcapacity * sizeof(hist_t));
            if(!histcurr.data.multiple.entry) error(lang[ERR_MEM]);
        }
        histcurr.data.multiple.entry[i].x = x;
        histcurr.data.multiple.entry[i].y = y;
        histcurr.data.multiple.entry[i].z = z;
        histcurr.data.multiple.entry[i].oldparam0 = op0;
        histcurr.data.multiple.entry[i].oldparam2 = op2;
        histcurr.data.multiple.entry[i].newparam0 = np0;
        histcurr.data.multiple.entry[i].newparam2 = np2;
    }
}

/**
 * Undo from history
 */
int hist_undo()
{
    int i, j, x, y, z;

    if(histtop < histmax) {
        switch(history[histtop].type) {
            case HIST_NODE:
                nodes[history[histtop].y][history[histtop].z][history[histtop].x].param0 = history[histtop].data.node.oldparam0;
                nodes[history[histtop].y][history[histtop].z][history[histtop].x].param2 = history[histtop].data.node.oldparam2;
            break;
            case HIST_BRUSH:
                for(i = 0; i < history[histtop].data.multiple.numentry; i++) {
                    nodes[history[histtop].data.multiple.entry[i].y]
                         [history[histtop].data.multiple.entry[i].z]
                         [history[histtop].data.multiple.entry[i].x].param0 = history[histtop].data.multiple.entry[i].oldparam0;
                    nodes[history[histtop].data.multiple.entry[i].y]
                         [history[histtop].data.multiple.entry[i].z]
                         [history[histtop].data.multiple.entry[i].x].param2 = history[histtop].data.multiple.entry[i].oldparam2;
                }
            break;
            case HIST_DELY:
                x = history[histtop].x;
                y = history[histtop].y;
                z = history[histtop].z;
                mts_addy(y, 0);
                for(i = j = 0; i < history[histtop].data.bulk.numentry; i++, j++, x++) {
                    if(j >= history[histtop].data.bulk.pitch) { j = 0; z++; x = history[histtop].x; }
                    memcpy(&nodes[y][z][x], &history[histtop].data.bulk.entry[i], sizeof(node_t));
                }
            break;
            case HIST_ADDY: mts_dely(history[histtop].y); break;
            case HIST_DELZ:
                x = history[histtop].x;
                y = history[histtop].y;
                z = history[histtop].z;
                mts_addz(z, 0);
                for(i = j = 0; i < history[histtop].data.bulk.numentry; i++, j++, x++) {
                    if(j >= history[histtop].data.bulk.pitch) { j = 0; y++; x = history[histtop].x; }
                    memcpy(&nodes[y][z][x], &history[histtop].data.bulk.entry[i], sizeof(node_t));
                }
            break;
            case HIST_ADDZ: mts_delz(history[histtop].z); break;
            case HIST_DELX:
                x = history[histtop].x;
                y = history[histtop].y;
                z = history[histtop].z;
                mts_addx(x, 0);
                for(i = j = 0; i < history[histtop].data.bulk.numentry; i++, j++, z++) {
                    if(j >= history[histtop].data.bulk.pitch) { j = 0; y++; z = history[histtop].z; }
                    memcpy(&nodes[y][z][x], &history[histtop].data.bulk.entry[i], sizeof(node_t));
                }
            break;
            case HIST_ADDX: mts_delx(history[histtop].x); break;
            case HIST_ROTCW: mts_rotate(1); break;
            case HIST_ROTCCW: mts_rotate(0); break;
            case HIST_FLIP: mts_flip(); break;
        }
        histtop++;
        status = nodes[currlayer][cz][cx].param0 ? blocks[nodes[currlayer][cz][cx].param0].name : NULL;
        return 1;
    }
    return 0;
}

/**
 * Redo from history
 */
int hist_redo()
{
    int i, j, x, y, z;

    if(histtop) {
        histtop--;
        switch(history[histtop].type) {
            case HIST_NODE:
                nodes[history[histtop].y][history[histtop].z][history[histtop].x].param0 = history[histtop].data.node.newparam0;
                nodes[history[histtop].y][history[histtop].z][history[histtop].x].param2 = history[histtop].data.node.newparam2;
            break;
            case HIST_BRUSH:
                for(i = 0; i < history[histtop].data.multiple.numentry; i++) {
                    nodes[history[histtop].data.multiple.entry[i].y]
                         [history[histtop].data.multiple.entry[i].z]
                         [history[histtop].data.multiple.entry[i].x].param0 = history[histtop].data.multiple.entry[i].newparam0;
                    nodes[history[histtop].data.multiple.entry[i].y]
                         [history[histtop].data.multiple.entry[i].z]
                         [history[histtop].data.multiple.entry[i].x].param2 = history[histtop].data.multiple.entry[i].newparam2;
                }
            break;
            case HIST_ADDY:
                x = history[histtop].x;
                y = history[histtop].y;
                z = history[histtop].z;
                mts_addy(y, 0);
                for(i = j = 0; i < history[histtop].data.bulk.numentry; i++, j++, x++) {
                    if(j >= history[histtop].data.bulk.pitch) { j = 0; z++; x = history[histtop].x; }
                    memcpy(&nodes[y][z][x], &history[histtop].data.bulk.entry[i], sizeof(node_t));
                }
            break;
            case HIST_DELY: mts_dely(history[histtop].y); break;
            case HIST_ADDZ:
                x = history[histtop].x;
                y = history[histtop].y;
                z = history[histtop].z;
                mts_addz(z, 0);
                for(i = j = 0; i < history[histtop].data.bulk.numentry; i++, j++, x++) {
                    if(j >= history[histtop].data.bulk.pitch) { j = 0; y++; x = history[histtop].x; }
                    memcpy(&nodes[y][z][x], &history[histtop].data.bulk.entry[i], sizeof(node_t));
                }
            break;
            case HIST_DELZ: mts_delz(history[histtop].z); break;
            case HIST_ADDX:
                x = history[histtop].x;
                y = history[histtop].y;
                z = history[histtop].z;
                mts_addx(x, 0);
                for(i = j = 0; i < history[histtop].data.bulk.numentry; i++, j++, z++) {
                    if(j >= history[histtop].data.bulk.pitch) { j = 0; y++; z = history[histtop].z; }
                    memcpy(&nodes[y][z][x], &history[histtop].data.bulk.entry[i], sizeof(node_t));
                }
            break;
            case HIST_DELX: mts_delx(history[histtop].x); break;
            case HIST_ROTCW: mts_rotate(0); break;
            case HIST_ROTCCW: mts_rotate(1); break;
            case HIST_FLIP: mts_flip(); break;
        }
        status = nodes[currlayer][cz][cx].param0 ? blocks[nodes[currlayer][cz][cx].param0].name : NULL;
        return 1;
    }
    return 0;
}

/**
 * Free history
 */
void hist_free(int s, int e)
{
    int i;

    if(s == -1) { s = 0; e = histmax; }
    for(i = s; i < e; i++)
        if(history[i].type == HIST_BRUSH && history[i].data.multiple.entry)
            free(history[i].data.multiple.entry);
        else if(history[i].type > HIST_BRUSH && history[i].data.bulk.entry)
            free(history[i].data.bulk.entry);
}

