/*
 * mtsedit/mts.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 MTS file format importer / exporter
 *
 */

#include "main.h"

char mtsfile[MAXPATHLEN];
/* block type mapping palettes */
int numpalettes = 0, lenpalettes = 0;
char **palettes = NULL;
/* blocks types */
int numblocks = 0;
mtsblock_t *blocks = NULL;

/* the actual MTS in memory */
int mts_x = 0, mts_y = 0, mts_z = 0;
int currlayer = 127, gndlayer = 0;
char layerprob[256];
node_t nodes[256][256][256];

/* generated properties */
int mix = 255, max = 0, miy = 255, may = 0, miz = 255, maz = 0, bound_valid = 0;

/**
 * Get schematic bounding cube and node id translation tables
 */
int mts_getbounds(int sanitize, unsigned short *tr, unsigned short *tr2)
{
    int x, y, z, j;

    if(!sanitize && !tr && bound_valid) return 0;

    /* get boundind box and block ID translation tables, sanitize probability values */
    j = 1; mix = 255; max = 0; miy = 255; may = 0; miz = 255; maz = 0;
    for(y = 0; y < 256; y++) {
        for(z = 0; z < 256; z++)
            for(x = 0; x < 256; x++) {
                if(nodes[y][z][x].param0) {
                    if(tr && tr2 && !tr2[nodes[y][z][x].param0]) {
                        tr2[nodes[y][z][x].param0] = j;
                        tr[j++] = nodes[y][z][x].param0;
                    }
                    if(x < mix) mix = x;
                    if(x > max) max = x;
                    if(y < miy) miy = y;
                    if(y > may) may = y;
                    if(z < miz) miz = z;
                    if(z > maz) maz = z;
                    if(sanitize && !(nodes[y][z][x].param1 & 0x7F)) nodes[y][z][x].param1 |= 127;
                } else
                    if(sanitize) nodes[y][z][x].param1 = 0;
            }
    }
    if(sanitize)
        for(y = miy; y <= may; y++)
            if(!layerprob[y]) layerprob[y] = 127;
    bound_valid = 1;
    return j;
}

/**
 * Load an MTS file
 */
void mts_load(unsigned char *data, unsigned int size)
{
    unsigned char *buff = NULL, *un = NULL, *b;
    unsigned int idx;
    int i, j, k, l, n, x, y, z, min_x = 0, min_y = 0, min_z = 0, *palref;
    unsigned short *tr = NULL;

    if(!data[4] && (data[5] == 3 || data[5] == 4) && !data[6] && data[7] && !data[8] && data[9] && !data[10] && data[11]) {
        mts_x = data[7]; mts_y = data[9]; mts_z = data[11];
        blocks[0].numref -= mts_y * mts_z * mts_x;
        min_x = 127 - mts_x / 2;
        min_y = 127 - mts_y / 2;
        min_z = 127 - mts_z / 2;
        buff = data + 12;
        /* guess if we have layer probability map or not for version 3 */
        if(buff[0] > 3 || data[5] == 4) {
            memcpy(layerprob + min_y, buff, mts_y);
            buff += mts_y;
        }
        n = (buff[0] << 8) | buff[1];
        if(n > 0 && n < 1024) {
            buff += 2;
            tr = (unsigned short*)malloc(n * sizeof(unsigned short));
            if(!tr) error(lang[ERR_MEM]);
            palref = (int*)malloc(numpalettes * sizeof(int));
            if(!palref) error(lang[ERR_MEM]);
            memset(palref, 0, numpalettes * sizeof(int));
            for(i = 0; i < n; i++) {
                tr[i] = numblocks;
                j = buff[1];
                buff += 2;
                for(k = 0; k < numblocks; k++)
                    for(l = 0; l < numpalettes + 3; l++)
                        if((!blocks[k].blocknames && (int)strlen(blocks[k].name) == j &&
                            !memcmp(blocks[k].name, buff, j)) || (blocks[k].blocknames && blocks[k].blocknames[l] &&
                            (int)strlen(blocks[k].blocknames[l]) == j && !memcmp(blocks[k].blocknames[l], buff, j))) {
                                if(k)
                                    switch(l) {
                                        case 0: break;
                                        case 1: blocks[k].dobiome = 1; savebiome = 1; break;
                                        case 2: if(!memcmp(buff, "mapgen", 6)) { savemapgen = 1; } break;
                                        default: palref[l - 3]++; break;
                                    }
                                tr[i] = k;
                                break;
                            }
                if(tr[i] == numblocks) {
                    x = buff[j]; buff[j] = 0;
                    fprintf(stderr, "mtsedit: %s: %s %d '%s'\r\n", mtsfile, lang[ERR_NODE], i, buff);
                    buff[j] = x;
                    idx = numblocks++;
                    blocks = (mtsblock_t*)realloc(blocks, numblocks * sizeof(mtsblock_t));
                    if(!blocks) error(lang[ERR_MEM]);
                    memset(&blocks[idx], 0, sizeof(mtsblock_t));
                    blocks[idx].name = (char*)malloc(j + 1);
                    if(!blocks[idx].name) error(lang[ERR_MEM]);
                    memcpy(blocks[idx].name, buff, j);
                    blocks[idx].name[j] = 0;
                }
                buff += j;
            }
            /* pick the node palette which has the most node reference */
            for(i = savepal = 0; i < numpalettes; i++)
                if(palref[i] > palref[savepal]) savepal = i;
            free(palref);
            size -= (int)(buff - data);
            un = (uint8_t*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)buff, size, 4096, &i, 1);
            if(un) { buff = un; size = (unsigned int)i; }
            if(4 * mts_x * mts_y * mts_z <= (int)size) {
                for(z = 0, b = buff; z < mts_z; z++)
                    for(y = 0; y < mts_y; y++)
                        for(x = 0; x < mts_x; x++, b += 2) {
                            nodes[y+min_y][mts_z+min_z-z][x+min_x].param0 = tr[(b[0] << 8) | b[1]];
                            blocks[nodes[y+min_y][mts_z+min_z-z][x+min_x].param0].numref++;
                        }
                for(z = 0; z < mts_z; z++)
                    for(y = 0; y < mts_y; y++)
                        for(x = 0; x < mts_x; x++, b++)
                            nodes[y+min_y][mts_z+min_z-z][x+min_x].param1 = data[5] == 3 ? b[0] >> 1 : b[0];
                for(z = 0; z < mts_z; z++)
                    for(y = 0; y < mts_y; y++)
                        for(x = 0; x < mts_x; x++, b++)
                            nodes[y+min_y][mts_z+min_z-z][x+min_x].param2 = b[0];
                gndlayer = currlayer = min_y;
                for(y = 0; y < mts_y; y++)
                    for(z = 0; z < mts_z; z++)
                        for(x = 0; x < mts_x; x++)
                            if(nodes[y+min_y][z+min_z][x+min_x].param2 == 0x20) {
                                gndlayer = y+min_y;
                                nodes[y+min_y][z+min_z][x+min_x].param2 = 0;
                            }
                status = lang[LOADED];
            } else mts_x = mts_y = mts_z = 0;
            free(tr);
            if(un) free(un);
        } else mts_x = mts_y = mts_z = 0;
    }
}

/**
 * Save an MTS file
 */
int mts_save()
{
    char outfile[MAXPATHLEN], *c;
    unsigned short *tr, *tr2;
    unsigned char *b, *b2, hdr[12];
    int i, j, k, x, y, z;
    FILE *f;

    status = lang[ERR_SAVE];
    tr = (unsigned short*)malloc(numblocks * sizeof(unsigned short));
    if(!tr) error(lang[ERR_MEM]);
    memset(tr, 0, numblocks * sizeof(unsigned short));
    tr2 = (unsigned short*)malloc(numblocks * sizeof(unsigned short));
    if(!tr2) error(lang[ERR_MEM]);
    memset(tr2, 0, numblocks * sizeof(unsigned short));

    j = mts_getbounds(1, tr, tr2);
    if(mix > max) { free(tr); free(tr2); return 1; }

    for(y = miy; y <= may; y++) {
        /* close windows and doors */
        for(z = miz+1; z < maz; z++)
            for(x = mix+1; x < max; x++)
                if(!nodes[y][z][x].param0 && !nodes[y][z][x].param1 && (
                    (nodes[y][z-1][x].param1 && (nodes[y][z+1][x].param1 || (z<254 && nodes[y][z+2][x].param1))) ||
                    (nodes[y][z][x-1].param1 && (nodes[y][z][x+1].param1 || (x<254 && nodes[y][z][x+2].param1)))))
                        nodes[y][z][x].param1 = 127;
        /* mark air in rooms temporarly */
        for(z = miz+1; z < maz; z++)
            for(x = mix+1; x < max; x++)
                if(!nodes[y][z][x].param0 && !nodes[y][z][x].param1 &&
                    (nodes[y][z-1][x].param1 || nodes[y][z][x-1].param1))
                        nodes[y][z][x].param1 = 1;
        /* clear rooms which are connected with the outside */
        do {
            k = 0;
            for(z = miz+1; z < maz; z++)
                for(x = mix+1; x < max; x++)
                    if(nodes[y][z][x].param1 == 1 &&
                        (!nodes[y][z-1][x].param1 || !nodes[y][z+1][x].param1 || !nodes[y][z][x-1].param1 ||
                         !nodes[y][z][x+1].param1)) {
                            nodes[y][z][x].param1 = 0;
                            k = 1;
                    }
        } while(k);
        /* make air in closed rooms permanent, also set the "force placement" flag for them */
        for(z = miz+1; z < maz; z++)
            for(x = mix+1; x < max; x++)
                if(nodes[y][z][x].param1 == 1) nodes[y][z][x].param1 = 255;
        /* mark ground level */
        if(y == gndlayer)
            for(z = miz; z <= maz; z++)
                for(x = mix; x <= max; x++)
                    if(!nodes[y][z][x].param0) nodes[y][z][x].param2 = 0x20;
    }

    /* get filename, buffer and open file  */
    strcpy(outfile, mtsfile);
    c = strrchr(outfile, '.');
    if(!c) c = &outfile[strlen(outfile)];
    strcpy(c, ".mts");
    f = fopen(outfile,"wb");
    if(!f) return 1;
    b = (unsigned char*)malloc((max-mix+1) * (may-miy+1) * (maz-miz+1) * 4);
    if(!b) error(lang[ERR_MEM]);
    memset(b, 0, (max-mix+1) * (may-miy+1) * (maz-miz+1) * 4);
    memset(hdr, 0, sizeof(hdr));

    /* write header */
    memcpy(hdr, "MTSM", 4);
    hdr[5] = 4;
    hdr[7] = max-mix+1;
    hdr[9] = may-miy+1;
    hdr[11] = maz-miz+1;
    fwrite(hdr, 12, 1, f);
    fwrite(layerprob + miy, may-miy+1, 1, f);

    /* write Name-ID table using the selected node palette */
    hdr[0] = 0; hdr[1] = j;
    fwrite(hdr, 2, 1, f);
    if(savepal < 0 || savepal >= numpalettes) savepal = 0;
    for(i = 0; i < j; i++) {
        c = !blocks[tr[i]].blocknames ? blocks[tr[i]].name : (savebiome && blocks[tr[i]].blocknames[1] &&
            blocks[tr[i]].dobiome ? blocks[tr[i]].blocknames[1] : (savemapgen && blocks[tr[i]].blocknames[2] &&
            blocks[tr[i]].blocknames[2][0] ? blocks[tr[i]].blocknames[2] : (blocks[tr[i]].blocknames[savepal+3] &&
            blocks[tr[i]].blocknames[savepal+3][0] ? blocks[tr[i]].blocknames[savepal+3] : blocks[tr[i]].name)));
        hdr[1] = strlen(c);
        fwrite(hdr, 2, 1, f);
        fwrite(c, hdr[1], 1, f);
    }

    /* construct param buffer */
    for(z = miz, b2 = b; z <= maz; z++)
        for(y = miy; y <= may; y++)
            for(x = mix; x <= max; x++, b2 += 2) {
                b2[0] = tr2[nodes[y][maz+miz-z][x].param0] >> 8;
                b2[1] = tr2[nodes[y][maz+miz-z][x].param0] & 0xFF;
            }
    for(z = miz; z <= maz; z++)
        for(y = miy; y <= may; y++)
            for(x = mix; x <= max; x++, b2++)
                *b2 = nodes[y][maz+miz-z][x].param1;
    for(z = miz; z <= maz; z++)
        for(y = miy; y <= may; y++)
            for(x = mix; x <= max; x++, b2++)
                *b2 = nodes[y][maz+miz-z][x].param2;
    /* compress and save */
    x = 1;
    b2 = stbi_zlib_compress(b, (max-mix+1) * (may-miy+1) * (maz-miz+1) * 4, &i, 9);
    if(b2) {
        fwrite(b2, i, 1, f);
        free(b2);
        status = lang[SAVED];
        x = 0;
    }
    fclose(f);
    free(b);
    free(tr);
    free(tr2);
    return x;
}

/**
 * Save a PNG preview file
 */
int mts_view(int type)
{
    int i, j, w, h, x, y, z, lh = 0;
    int r = 0, k, n, l;
    char pngfile[MAXPATHLEN], *c;
    unsigned char *buff = NULL;
    SDL_Surface *preview, *blk;
    SDL_Rect src, dst;
    FILE *f;

    status = lang[ERR_PREVIEW];
    mts_getbounds(0, NULL, NULL);
    if(mix > max) return 1;
    bound_valid = 0;

    /* calculate dimensions and create images */
    n = (max - mix) + (maz - miz) + 1;
    w = n * dx + 16;
    lh = n * dz + 32 - dz;
    l = 32 - 2*(_y0 + dz);
    h = lh + (may - miy) * l;
    blk = SDL_CreateRGBSurfaceFrom(NULL, 32, 32, 32, 32*4, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);
    preview = SDL_CreateRGBSurface(0, w, h, 32, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);
    src.x = src.y = 0; src.w = src.h = dst.w = dst.h = 32;
    n = ((max-mix)+(maz-miz))/2;

    /* generate preview into surface */
    for(y = miy; y <= may; y++)
        for(z = miz; z <= maz; z++) {
            if(type) r = ((z-miz+1)*(max-mix+1)*(y-miy)/(maz-miz+1))/(may-miy);
            for(x = mix; x <= max - r; x++) {
                if(nodes[y][z][x].param0) {
                    dst.x = dx * ((x-mix) - (z-miz)) + (maz-miz) * dx;
                    dst.y = h - lh + dz * ((z-miz) + (x-mix)) - (y-miy) * l;
                    i = nodes[y][z][x].param0;
                    if(i && blocks[i].img) {
                        j = nodes[y][z][x].param2;
                        j %= (int)blocks[i].numpar2;
                        memcpy(tmpblk, blocks[i].img + j * 32 * 32 * 4, 32 * 32 * 4);
                        for(j = 0; j < 32 * 32 * 4; j += 4) {
                            k = (int)(tmpblk[j+0]) * ((x-mix)+(z-miz)) / n;
                            tmpblk[j+0] = k > 255 ? 255 : (k > tmpblk[j+0] / 2 ? k : tmpblk[j+0] / 2);
                            k = (int)(tmpblk[j+1]) * ((x-mix)+(z-miz)) / n;
                            tmpblk[j+1] = k > 255 ? 255 : (k > tmpblk[j+1] / 2 ? k : tmpblk[j+1] / 2);
                            k = (int)(tmpblk[j+2]) * ((x-mix)+(z-miz)) / n;
                            tmpblk[j+2] = k > 255 ? 255 : (k > tmpblk[j+2] / 2 ? k : tmpblk[j+2] / 2);
                        }
                        blk->pixels = tmpblk;
                        SDL_BlitSurface(blk, &src, preview, &dst);
                    }
                }
            }
        }
    SDL_FreeSurface(blk);

    /* crop the result */
    buff = preview->pixels;
    for(i = miy = 0; i < preview->pitch * h; i += 4)
        if(buff[i + 3]) { miy = i / preview->pitch; break; }
    for(i = may = preview->pitch * preview->h - 4; i ; i -= 4)
        if(buff[i + 3]) { may = i / preview->pitch; break; }
    for(i = 0, mix = -1; i < w/2 && mix == -1; i++)
        for(j = 0; j < h; j++)
            if(buff[j * preview->pitch + i * 4 + 3]) { mix = i; break; }
    for(i = w-1, max = -1; i > w/2 && max == -1; i--)
        for(j = 0; j < h; j++)
            if(buff[j * preview->pitch + i * 4 + 3]) { max = i; break; }
    if(mix >= 0 && max > 0 && may > miy) {
        blk = SDL_CreateRGBSurface(0, max-mix+1, may-miy+1, 32, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);
        src.x = mix; src.y = miy;
        dst.x = dst.y = 0;
        src.w = dst.w = blk->w;
        src.h = dst.h = blk->h;
        SDL_BlitSurface(preview, &src, blk, &dst);
        SDL_FreeSurface(preview);

        /* write result to PNG file */
        buff = (unsigned char *)stbi_write_png_to_mem(blk->pixels, blk->pitch, blk->w, blk->h, 4, &i);
        SDL_FreeSurface(blk);
        if(buff) {
            strcpy(pngfile, mtsfile);
            c = strrchr(pngfile, '.');
            if(!c) c = &pngfile[strlen(pngfile)];
            strcpy(c, ".png");
            f = fopen(pngfile,"wb");
            if(f) {
                fwrite(buff, i, 1, f);
                fclose(f);
                status = lang[PREVIEWSAVED];
                fprintf(stderr, "mtsedit: %s: %s\r\n", pngfile, status);
            }
            free(buff);
        }
        return 0;
    }
    SDL_FreeSurface(preview);
    return 1;
}

/**
 * Export a PNG blueprint file
 */
int mts_blueprint(int type)
{
    int oldsmax = strmaxw, i, j, k, l, bw, w, h, x, y, z;
    char pngfile[MAXPATHLEN], *s, *e, *c;
    unsigned char *buff = NULL;
    uint32_t *dst;
    SDL_Surface *oldscr = screen;
    SDL_Rect rect;
    FILE *f;

    status = lang[ERR_PREVIEW];
    mts_getbounds(0, NULL, NULL);
    if(mix > max) return 1;
    bound_valid = 0;

    strcpy(pngfile, mtsfile);
    s = strrchr(pngfile, DIRSEP);
    if(!s) s = pngfile; else s++;
    e = strrchr(pngfile, '.');
    if(!e) e = &pngfile[strlen(pngfile)];

    /* calculate dimensions and create image */
    bw = maz - miz;
    h = (may - miy + 1) * (max - mix + 2) + 3;
    if(!type) {
        for(i = 1, j = l = 0; i < numblocks; i++)
            if(blocks[i].numref) {
                l += font->height;
                k = mbstrlen(blocks[i].name);
                if(k > j) j = k;
            }
        w = bw + 8 + (4 + j) * (font->width + 1);
        if(l > h) h = l;
        h += font->height + 5;
    } else
        w = bw + 3;
    screen = SDL_CreateRGBSurface(0, w, h, 32, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);
    memset(screen->pixels, 0, w * h * 4);
    dst = (uint32_t*)screen->pixels;
    if(!type) {
        for(i = 0; i < w * h; i++) dst[i] = theme[THEME_BP_BG];

        /* print title */
        for(c = s; c < e; c++)
            if(*c == '_') *c = ' ';
        *e = 0; strmaxw = screen->w - 1;
        i = mbstrlen(s) * (font->width+1);
        sdlprint((screen->w - (i < screen->w ? i : screen->w)) / 2, 0, THEME_BP_FG, THEME_BP_BG, s);

        /* print legend */
        rect.x = bw + font->height; rect.y = font->height + 4; rect.w = rect.h = font->height - 2;
        for(i = 1; i < numblocks; i++)
            if(blocks[i].numref) {
                SDL_FillRect(screen, &rect, blocks[i].color);
                sdlprint(rect.x + 4 + font->height, rect.y, THEME_BP_FG, THEME_BP_BG, blocks[i].name);
                rect.y += font->height;
            }
        i = w * (font->height + 5);
    } else
        i = w;
    /* generate blueprint into surface */
    for(y = may; y >= miy; y--, i += w) {
        dst[i] = (y == gndlayer ? 0xFF0000FF : 0xFF000000);
        for(x = mix; x <= max; x++) {
            i++;
            for(z = maz; z >= miz; z--, i++)
                dst[i] = (nodes[y][z][x].param0 ? blocks[nodes[y][z][x].param0].color : 0);
            if(y == gndlayer)
                dst[i] = 0xFF0000FF;
            i += w - bw - 2;
        }
    }

    /* write result to PNG file */
    buff = (unsigned char *)stbi_write_png_to_mem(screen->pixels, screen->pitch, screen->w, screen->h, 4, &i);
    SDL_FreeSurface(screen);
    screen = oldscr;
    strmaxw = oldsmax;
    if(buff) {
        strcpy(pngfile, mtsfile);
        strcpy(e, "_bp.png");
        f = fopen(pngfile,"wb");
        if(f) {
            fwrite(buff, i, 1, f);
            fclose(f);
            status = lang[BPRINTSAVED];
            fprintf(stderr, "mtsedit: %s: %s\r\n", pngfile, status);
        }
        free(buff);
        return 0;
    }
    return 1;
}

/**
 * Dump structure
 */
int mts_dump()
{
    unsigned short *tr, *tr2;
    int i, j, x, y, z;

    tr = (unsigned short*)malloc(numblocks * sizeof(unsigned short));
    if(!tr) error(lang[ERR_MEM]);
    memset(tr, 0, numblocks * sizeof(unsigned short));
    tr2 = (unsigned short*)malloc(numblocks * sizeof(unsigned short));
    if(!tr2) error(lang[ERR_MEM]);
    memset(tr2, 0, numblocks * sizeof(unsigned short));

    j = mts_getbounds(0, tr, tr2);
    if(mix > max) { free(tr); free(tr2); return 1; }

    printf("-------------------------------- %s --------------------------------\r\n", mtsfile);
    printf("Map (x: %d y: %d z: %d):\r\n", max-mix+1, may-miy+1, maz-miz+1);
    if(savepal < 0 || savepal >= numpalettes) savepal = 0;
    for(i = 0; i < j; i++)
        printf("%4x: %s\r\n", i, blocks[tr[i]].blocknames && blocks[tr[i]].blocknames[savepal+3] ?
            blocks[tr[i]].blocknames[savepal+3] : blocks[tr[i]].name);

    for(y = miy; y <= may; y++) {
        printf("\r\nLayer %d (probability %d%s):\r\n", y - miy, layerprob[y], y == gndlayer ? ", Ground level" : "");
        for(z = miz; z <= maz; z++) {
            if(j < 15) printf(" ");
            for(x = mix; x <= max; x++) {
                if(nodes[y][z][x].param0) {
                    if(j < 16) printf("%x", tr2[nodes[y][z][x].param0]);
                    else printf(" %02x", tr2[nodes[y][z][x].param0]);
                } else printf(j < 16 ? "." : " ..");
            }
            printf("  ");
            for(x = mix; x <= max; x++)
                printf(" %02x", nodes[y][z][x].param1);
            printf("  ");
            for(x = mix; x <= max; x++)
                printf(" %02x", y == gndlayer && !nodes[y][z][x].param0 ? 32 : nodes[y][z][x].param2);
            printf("\r\n");
        }
    }

    free(tr);
    free(tr2);
    return 0;
}

/**
 * Change layer probability
 */
void mts_layerprob(int diff)
{
    if(diff < 0 && (int)layerprob[currlayer] > 0)   layerprob[currlayer]--;
    if(diff > 0 && (int)layerprob[currlayer] < 127) layerprob[currlayer]++;
}

/**
 * Rotate the entire MTS structure
 */
void mts_rotate(int ccw)
{
    int nx, nz, x, y, z, o;
    node_t layer[256][256];

    mts_getbounds(0, NULL, NULL);
    if(mix > max) return;

    for(y = miy; y <= may; y++) {
        memcpy(layer, nodes[y], sizeof(node_t)*256*256);
        memset(nodes[y], 0, sizeof(node_t)*256*256);
        for(z = miz; z <= maz; z++)
            for(x = mix; x <= max; x++) {
                if(ccw) { nz = 255-x; nx = z; } else { nz = x; nx = 255-z; }
                memcpy(&nodes[y][nz][nx], &layer[z][x], sizeof(node_t));
                if(nodes[y][nz][nx].param0 && blocks[nodes[y][nz][nx].param0].rotate) {
                    o = nodes[y][nz][nx].param2;
                    if(ccw)
                        nodes[y][nz][nx].param2 = (nodes[y][nz][nx].param2 & ~3) | (((nodes[y][nz][nx].param2 & 3)-1) & 3);
                    else
                        nodes[y][nz][nx].param2 = (nodes[y][nz][nx].param2 & ~3) | (((nodes[y][nz][nx].param2 & 3)+1) & 3);
                    if(o != nodes[y][nz][nx].param2) {
                        hist_prepare(HIST_NODE, 0);
                        hist_add(y, nz, nx, nodes[y][nz][nx].param0, o, nodes[y][nz][nx].param0, nodes[y][nz][nx].param2);
                        hist_commit();
                    }
                }
            }
    }
    bound_valid = 0;
    mts_getbounds(0, NULL, NULL);
}

/**
 * Flip the entire MTS structure along the Z axis
 */
void mts_flip()
{
    int x, y, z, o;
    node_t layer[256][256];

    mts_getbounds(0, NULL, NULL);
    if(mix > max) return;

    for(y = miy; y <= may; y++) {
        memcpy(layer, nodes[y], sizeof(node_t)*256*256);
        for(z = miz; z <= maz; z++)
            for(x = mix; x <= max; x++) {
                memcpy(&nodes[y][z][x], &layer[maz-z+miz][x], sizeof(node_t));
                if(nodes[y][z][x].param0 && blocks[nodes[y][z][x].param0].rotate) {
                    o = nodes[y][z][x].param2;
                    nodes[y][z][x].param2 = (nodes[y][z][x].param2 & ~3) | (((nodes[y][z][x].param2 & 3)+2) & 3);
                    if(o != nodes[y][z][x].param2) {
                        hist_prepare(HIST_NODE, 0);
                        hist_add(y, z, x, nodes[y][z][x].param0, o, nodes[y][z][x].param0, nodes[y][z][x].param2);
                        hist_commit();
                    }
                }
            }
    }
    bound_valid = 0;
    mts_getbounds(0, NULL, NULL);
}

/**
 * Insert an Y layer
 */
void mts_addy(int y, int copy)
{
    int x, Y, z;

    mts_getbounds(0, NULL, NULL);
    if(miy > may || y < miy || y > may) return;

    for(Y = miy-1 < 0 ? 0 : miy-1; Y < y; Y++)
        for(z = miz; z <= maz; z++)
            for(x = mix; x <= max; x++)
                memcpy(&nodes[Y][z][x], &nodes[Y+1][z][x], sizeof(node_t));
    if(!copy)
        for(z = miz; z <= maz; z++)
            for(x = mix; x <= max; x++)
                memset(&nodes[y][z][x], 0, sizeof(node_t));
    if(miy) miy--;
}

/**
 * Remove an Y layer
 */
void mts_dely(int y)
{
    int x, Y, z;

    mts_getbounds(0, NULL, NULL);
    if(miy > may || y < miy || y > may) return;

    for(Y = y; Y >= miy; Y--)
        for(z = miz; z <= maz; z++)
            for(x = mix; x <= max; x++)
                memcpy(&nodes[Y][z][x], &nodes[Y-1][z][x], sizeof(node_t));
    for(z = miz; z <= maz; z++)
        for(x = mix; x <= max; x++)
            memset(&nodes[miy][z][x], 0, sizeof(node_t));
    if(miy < y) miy++;
}

/**
 * Insert an X layer
 */
void mts_addx(int x, int copy)
{
    int X, y, z;

    mts_getbounds(0, NULL, NULL);
    if(mix > max || x < mix || x > max) return;

    for(y = miy; y <= may; y++)
        for(z = miz; z <= maz; z++)
            for(X = max+1 > 255 ? 255 : max+1; X > x ; X--)
                memcpy(&nodes[y][z][X], &nodes[y][z][X-1], sizeof(node_t));
    if(!copy)
        for(y = miy; y <= may; y++)
            for(z = miz; z <= maz; z++)
                memset(&nodes[y][z][x], 0, sizeof(node_t));
    if(max < 255) max++;
}

/**
 * Remove an X layer
 */
void mts_delx(int x)
{
    int X, y, z;

    mts_getbounds(0, NULL, NULL);
    if(mix > max || x < mix || x > max) return;

    for(y = miy; y <= may; y++)
        for(z = miz; z <= maz; z++)
            for(X = x; X <= max; X++)
                memcpy(&nodes[y][z][X], &nodes[y][z][X+1], sizeof(node_t));
    for(y = miy; y <= may; y++)
        for(z = miz; z <= maz; z++)
            memset(&nodes[y][z][max], 0, sizeof(node_t));
    if(max > x) max--;
}

/**
 * Insert a Z layer
 */
void mts_addz(int z, int copy)
{
    int x, y, Z;

    mts_getbounds(0, NULL, NULL);
    if(miz > maz || z < miz || z > maz) return;

    for(y = miy; y <= may; y++)
        for(Z = miz-1 < 0 ? 0 : miz-1; Z < z; Z++)
            for(x = mix; x <= max; x++)
                memcpy(&nodes[y][Z][x], &nodes[y][Z+1][x], sizeof(node_t));
    if(!copy)
        for(y = miy; y <= may; y++)
            for(x = mix; x <= max; x++)
                memset(&nodes[y][z][x], 0, sizeof(node_t));
    if(miz) miz--;
}

/**
 * Remove a Z layer
 */
void mts_delz(int z)
{
    int x, y, Z;

    mts_getbounds(0, NULL, NULL);
    if(miz > maz || z < miz || z > maz) return;

    for(y = miy; y <= may; y++)
        for(Z = z; Z >= miz; Z--)
            for(x = mix; x <= max; x++)
                memcpy(&nodes[y][Z][x], &nodes[y][Z-1][x], sizeof(node_t));
    for(y = miy; y <= may; y++)
        for(x = mix; x <= max; x++)
            memset(&nodes[y][miz][x], 0, sizeof(node_t));
    if(miz < z) miz++;
}
