/*
 * mtsedit/brush.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 Select Brush window and other brush functions
 *
 */

#include "main.h"

#define BRUSHTYPEMAX 7
#define BRUSHCOORDMAX 127

int brush_type = 0, brush_hollow = 0, brush_x = 1, brush_y = 1, brush_z = 1;
int brushfld = 0, brushmax = 0;

char brushmask[BRUSHCOORDMAX*BRUSHCOORDMAX/8], brushmaskmax[BRUSHCOORDMAX];

/**
 * Set the selected brush's mask. Only needed for globe really
 */
void brush_setmask(int needmax)
{
    int i, x, y = 0, r, err;

    memset(brushmask, 0, sizeof(brushmask));
    if(needmax) memset(brushmaskmax, 0, sizeof(brushmaskmax));
    if(brush_type && brush_type < 4 && brush_x >= 1) {
        brush_x--;
        /* one might think that it would be enough to solve the circle's equation, but they couldn't be more wrong. There are
         * problems with rounding and accumulated errors, also there are spurious pixels. So we draw into a bitmask instead. */
        x = -brush_x; err = 2 - 2 * brush_x;
        do {
            if(needmax && brushmaskmax[y] < -x) brushmaskmax[y] = -x;
            if(brush_hollow)
                brushmask[(y<<4)|((-x)>>3)] |= 1<<((-x)&7);
            else
                for(i = 0; i <= -x; i++)
                    brushmask[(y<<4)|(i>>3)] |= 1<<(i&7);
            r = err;
            if (r <= y) err += ++y * 2 + 1;
            if (r > x || err > y) err += ++x * 2 + 1;
        } while (x <= 0);
        brush_x++;
    }
}

/**
 * Insert a brush at cursor position
 */
void brush_place(int blk)
{
    int x, z, y, i, j;

    switch(brush_type) {
        case 0:
            /* single node */
            hist_prepare(HIST_NODE, 0);
            edit_placenode(currlayer, cz, cx, blk);
        break;
        case 1:
            /* globe */
            hist_prepare(HIST_BRUSH, 2 * brush_x * 2 * brush_x * 2 * brush_x);
            i = brush_x;
            for(y = 0; brushmaskmax[y]; y++) {
                if(brushmaskmax[y] + 1 != brush_x) {
                    brush_x = brushmaskmax[y] + 1;
                    brush_setmask(0);
                }
                for(z = 0; z <= brush_x; z++)
                    for(x = 0; x <= brush_x; x++)
                        if(brushmask[(z<<4)|(x>>3)] & (1<<(x&7))) {
                            edit_placenode(currlayer + y, cz - z, cx + x, blk);
                            edit_placenode(currlayer + y, cz + z, cx + x, blk);
                            edit_placenode(currlayer + y, cz - z, cx - x, blk);
                            edit_placenode(currlayer + y, cz + z, cx - x, blk);
                            edit_placenode(currlayer - y, cz - z, cx + x, blk);
                            edit_placenode(currlayer - y, cz + z, cx + x, blk);
                            edit_placenode(currlayer - y, cz - z, cx - x, blk);
                            edit_placenode(currlayer - y, cz + z, cx - x, blk);
                            if(brush_hollow) {
                                edit_placenode(currlayer + x, cz - z, cx + y, blk);
                                edit_placenode(currlayer + x, cz + z, cx + y, blk);
                                edit_placenode(currlayer + x, cz - z, cx - y, blk);
                                edit_placenode(currlayer + x, cz + z, cx - y, blk);
                                edit_placenode(currlayer - x, cz - z, cx + y, blk);
                                edit_placenode(currlayer - x, cz + z, cx + y, blk);
                                edit_placenode(currlayer - x, cz - z, cx - y, blk);
                                edit_placenode(currlayer - x, cz + z, cx - y, blk);
                            }
                        }
            }
            brush_x = i;
            brush_setmask(0);
        break;
        case 2:
            /* dome */
            hist_prepare(HIST_BRUSH, 2 * brush_x * 2 * brush_x * (brush_x + 1));
            i = brush_x;
            for(y = 0; brushmaskmax[y]; y++) {
                if(brushmaskmax[y] + 1 != brush_x) {
                    brush_x = brushmaskmax[y] + 1;
                    brush_setmask(0);
                }
                for(z = 0; z <= brush_x; z++)
                    for(x = 0; x <= brush_x; x++)
                        if(brushmask[(z<<4)|(x>>3)] & (1<<(x&7))) {
                            edit_placenode(currlayer + y, cz - z, cx + x, blk);
                            edit_placenode(currlayer + y, cz + z, cx + x, blk);
                            edit_placenode(currlayer + y, cz - z, cx - x, blk);
                            edit_placenode(currlayer + y, cz + z, cx - x, blk);
                            if(brush_hollow) {
                                edit_placenode(currlayer + x, cz - z, cx + y, blk);
                                edit_placenode(currlayer + x, cz + z, cx + y, blk);
                                edit_placenode(currlayer + x, cz - z, cx - y, blk);
                                edit_placenode(currlayer + x, cz + z, cx - y, blk);
                            }
                        }
            }
            brush_x = i;
            brush_setmask(0);
        break;
        case 3:
            /* cylinder */
            hist_prepare(HIST_BRUSH, brush_x * brush_x * brush_x);
            for(y = 0; y < brush_z; y++)
                for(z = 0; z <= brush_x; z++)
                    for(x = 0; x <= brush_x; x++)
                        if(brushmask[(z<<4)|(x>>3)] & (1<<(x&7))) {
                            edit_placenode(currlayer + y, cz - z, cx + x, blk);
                            edit_placenode(currlayer + y, cz + z, cx + x, blk);
                            edit_placenode(currlayer + y, cz - z, cx - x, blk);
                            edit_placenode(currlayer + y, cz + z, cx - x, blk);
                        }
        break;
        case 4:
            /* cuboid */
            hist_prepare(HIST_BRUSH, brush_x * brush_x * brush_x);
            for(y = 0; y < brush_y; y++)
                for(z = 0; z < brush_z; z++)
                    for(x = 0; x < brush_x; x++)
                        if(!brush_hollow || (brush_y > 1 && (!y || y == brush_y - 1)) ||
                            !x || !z || x == brush_x - 1 || z == brush_z - 1)
                            edit_placenode(currlayer + y, cz - z, cx + x, blk);
        break;
        case 5:
            /* pyramid */
            hist_prepare(HIST_BRUSH, brush_x * brush_x * brush_x);
            for(y = 0; y <= brush_y; y++) {
                i = (brush_z - 1) * y / brush_y;
                j = (brush_x - 1) * y / brush_y;
                for(z = i; z < brush_z - i; z++)
                    for(x = j; x < brush_x - j; x++)
                        if(!brush_hollow || !y || x == j || z == i || x == brush_x - j - 1 || z == brush_z - i - 1)
                            edit_placenode(currlayer + y, cz - z, cx + x, blk);
            }
        break;
        case 6:
            /* upside-down pyramid */
            hist_prepare(HIST_BRUSH, brush_x * brush_x * brush_x);
            for(y = 0; y <= brush_y; y++) {
                i = (brush_z - 1) * y / brush_y;
                j = (brush_x - 1) * y / brush_y;
                for(z = i; z < brush_z - i; z++)
                    for(x = j; x < brush_x - j; x++)
                        if(!brush_hollow || !y || x == j || z == i || x == brush_x - j - 1 || z == brush_z - i - 1)
                            edit_placenode(currlayer - y, cz - z, cx + x, blk);
            }
        break;
        case 7:
            /* roof / tent */
            hist_prepare(HIST_BRUSH, brush_x * brush_x * brush_x);
            for(y = 0; y <= brush_y; y++) {
                j = (brush_x - 1) * y / brush_y;
                for(z = 0; z < brush_z; z++)
                    for(x = j; x < brush_x - j; x++)
                        if(!brush_hollow || !y || x == j || !z || x == brush_x - j - 1 || z == brush_z - 1)
                            edit_placenode(currlayer + y, cz - z, cx + x, blk);
            }
        break;
    }
    hist_commit();
}

/**
 * Reentrant filler
 */
void brush_dofill(int y, int z, int x, int old, int blk)
{
    if(x >= 0 && x < 256 && y >= 0 && y < 256 && z >=0 && z < 256 && nodes[y][z][x].param0 == old) {
        edit_placenode(y, z, x, blk);
        nodes[y][z][x].param2 = 0;
        brush_dofill(y - 1, z, x, old, blk);
        brush_dofill(y + 1, z, x, old, blk);
        brush_dofill(y, z - 1, x, old, blk);
        brush_dofill(y, z + 1, x, old, blk);
        brush_dofill(y, z, x - 1, old, blk);
        brush_dofill(y, z, x + 1, old, blk);
    }
}

/**
 * Replace an entire area of the same block
 */
void brush_floodfill(int blk)
{
    if(nodes[currlayer][cz][cx].param0 && blk != nodes[currlayer][cz][cx].param0 && nodes[currlayer][cz][cx].param0) {
        hist_prepare(HIST_BRUSH, 1024);
        brush_dofill(currlayer, cz, cx, nodes[currlayer][cz][cx].param0, blk);
        hist_commit();
    }
}

/**
 * Return brush's height
 */
int brush_height()
{
    return !brush_type ? 1 : (brush_type < 3 ? brush_x : (brush_type == 3 ? brush_z : brush_y));
}

/**
 * Returns true if coordinates are inside the brush or on the edge (if hollow)
 */
int brush_selected(int x, int z)
{
    int X = x - cx, Z = cz - z;
    if(!brush_type || activetool != -1) return (!X && !Z);
    if(brush_type && brush_type < 4) {
        if(X < 0) X = -X;
        if(Z < 0) Z = -Z;
        return X > brush_x || Z > brush_x ? 0 : brushmask[(Z<<4)|(X>>3)] & (1<<(X&7));
    }
    return x < cx || z > cz || X >= brush_x || Z >= brush_z ? 0 :
        (brush_hollow ? (!X || X == brush_x - 1 || !Z || Z == brush_z - 1) : 1);
}

/**
 * Decrease one of the brush parameters
 */
void brush_dec()
{
    switch(brushfld) {
        case 0: if(brush_type) brush_type--; else brush_type = BRUSHTYPEMAX; break;
        case 1: brush_hollow ^= 1; break;
        case 2: if(brush_x > 1) brush_x--; break;
        case 3: if(brush_z > 1) brush_z--; break;
        case 4: if(brush_y > 1) brush_y--; break;
    }
}

/**
 * Increase one of the brush parameters
 */
void brush_inc()
{
    switch(brushfld) {
        case 0: if(brush_type < BRUSHTYPEMAX) brush_type++; else brush_type = 0; break;
        case 1: brush_hollow ^= 1; break;
        case 2: if(brush_x < BRUSHCOORDMAX) brush_x++; break;
        case 3: if(brush_z < BRUSHCOORDMAX) brush_z++; break;
        case 4: if(brush_y < BRUSHCOORDMAX) brush_y++; break;
    }
}

/**
 * Redraw the Select Brush window
 */
void brush_redraw()
{
    char str[8] = "\xC2\x80\xC2\x80";
    SDL_Rect src, dst;

    brushmax = !brush_type ? 1 : (brush_type < 3 ? 3 : (brush_type == 3 ? 4 : 5));

    src.x = src.y = 32; dst.y = 0; dst.x = 36; src.w = dst.w = bg->w - 64; src.h = dst.h = bg->h - 64;
    SDL_BlitSurface(bg, &src, screen, &dst);
    SDL_BlitSurface(cl, &src, screen, &dst);
    SDL_BlitSurface(fg, &src, screen, &dst);

    dst.x = 38; dst.h = brushmax * (font->height + 2) + 6; dst.y = 84 + (3 * 36); dst.w = 128 + 6 * font->width + 6;
    src.x = dst.x + 128; src.h = font->height;
    SDL_FillRect(screen, &dst, theme[THEME_BG]);

    strmaxw = 34 + dst.w; dst.y += 4;
    sdlprint(dst.x + 4, dst.y, THEME_FG, THEME_BG, lang[TYPE]);
    src.y = dst.y;
    src.w = 4 * font->width + 4;
    SDL_FillRect(screen, &src, theme[THEME_INPBG]);
    sdlprint(src.x, dst.y, THEME_BG, !brushfld ? THEME_INPUT : THEME_FG, "\033");
    strsepar = 0;
    str[1] = 0x80 + (4*brush_type) + (2*brush_hollow);
    str[3] = 0x81 + (4*brush_type) + (2*brush_hollow);
    sdlprint(src.x + font->width + font->width/2 + 1, dst.y, !brushfld ? THEME_INPUT : THEME_FG, THEME_INPBG, str);
    sdlprint(src.x + 4*font->width + 4, dst.y, THEME_BG, !brushfld ? THEME_INPUT : THEME_FG, "\032");
    strsepar = 1; dst.y += font->height + 2;

    if(brush_type) {
        sdlprint(dst.x + 4, dst.y, THEME_FG, THEME_BG, lang[HOLLOW]);
        src.y = dst.y;
        src.w = font->width + font->width/2 + 2;
        SDL_FillRect(screen, &src, theme[THEME_INPBG]);
        sdlprint(src.x + font->width/4 + 1, src.y, brushfld == 1 ? THEME_INPUT : THEME_FG, THEME_INPBG, brush_hollow ? "x" : " ");
        dst.y += font->height + 2;

        sdlprint(dst.x + 4, dst.y, THEME_FG, THEME_BG, brush_type < 4 ? lang[RADIUS] : lang[WIDTH]);
        src.y = dst.y;
        src.w = 4 * font->width + 4;
        SDL_FillRect(screen, &src, theme[THEME_INPBG]);
        sprintf(str, "%3d", brush_x);
        sdlprint(src.x, dst.y, THEME_BG, brushfld == 2 ? THEME_INPUT : THEME_FG, "-");
        sdlprint(src.x + font->width + 1, dst.y, brushfld == 2 ? THEME_INPUT : THEME_FG, THEME_INPBG, str);
        sdlprint(src.x + 4*font->width + 4, dst.y, THEME_BG, brushfld == 2 ? THEME_INPUT : THEME_FG, "+");
        dst.y += font->height + 2;

        if(brush_type > 2) {
            sdlprint(dst.x + 4, dst.y, THEME_FG, THEME_BG, brush_type == 3 ? lang[HEIGHT] : lang[LENGTH]);
            src.y = dst.y;
            src.w = 4 * font->width + 4;
            SDL_FillRect(screen, &src, theme[THEME_INPBG]);
            sprintf(str, "%3d", brush_z);
            sdlprint(src.x, dst.y, THEME_BG, brushfld == 3 ? THEME_INPUT : THEME_FG, "-");
            sdlprint(src.x + font->width + 1, dst.y, brushfld == 3 ? THEME_INPUT : THEME_FG, THEME_INPBG, str);
            sdlprint(src.x + 4*font->width + 4, dst.y, THEME_BG, brushfld == 3 ? THEME_INPUT : THEME_FG, "+");
            dst.y += font->height + 2;

            if(brush_type > 3) {
                sdlprint(dst.x + 4, dst.y, THEME_FG, THEME_BG, lang[HEIGHT]);
                src.y = dst.y;
                src.w = 4 * font->width + 4;
                SDL_FillRect(screen, &src, theme[THEME_INPBG]);
                sprintf(str, "%3d", brush_y);
                sdlprint(src.x, dst.y, THEME_BG, brushfld == 4 ? THEME_INPUT : THEME_FG, "-");
                sdlprint(src.x + font->width + 1, dst.y, brushfld == 4 ? THEME_INPUT : THEME_FG, THEME_INPBG, str);
                sdlprint(src.x + 4*font->width + 4, dst.y, THEME_BG, brushfld == 4 ? THEME_INPUT : THEME_FG, "+");
                dst.y += font->height + 2;
            }
        }
    }
}

/**
 * Select Brush window mouse down event handler
 */
int brush_mousedown(SDL_Event *event)
{
    if(event->button.x > 38 + 128 + 6 * (int)font->width + 6 || event->button.y < 84 + (3 * 36) ||
        event->button.y > brushmax * ((int)font->height + 2) + 8 + 84 + (3 * 36)) {
        brush_setmask(1);
        return 0;
    }
    brushfld = (event->button.y - 84 - (3 * 36) - 4) / (font->height + 2);
    if(event->button.x >= 38+128 && event->button.x < 38+128 + 2 * (int)font->width) brush_dec();
    else if(event->button.x >= 38+128 + 2 * (int)font->width) brush_inc();
    brush_redraw();
    return 1;
}

/**
 * Select Brush window keypress event handler
 */
void brush_key(SDL_Event *event)
{
    switch(event->type) {
        case SDL_KEYUP:
            switch (event->key.keysym.sym) {
                case SDLK_UP: if(!brushfld) brushfld = brushmax - 1; else brushfld--; break;
                case SDLK_DOWN: if(brushfld == brushmax - 1) brushfld = 0; else brushfld++; break;
                case SDLK_LEFT: brush_dec(); break;
                case SDLK_RIGHT: brush_inc(); break;
                case SDLK_SPACE: brush_hollow ^= 1; break;
                default:
                    brush_setmask(1);
                    sdldo(-1);
                break;
            }
        break;
    }
}
