/*
 * mtsedit/blocks.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 Dealing with the data directory and blocks.csv
 *
 */

#include "main.h"

char *path = NULL, *fn = NULL, *remapfile = NULL, noblocknametranslate = 0;
#ifndef __WIN32__
char *home = NULL;
#endif

/**
 * Get blocks.csv's absolute path
 */
void blocks_getdir(char **argv, char **envp)
{
    FILE *f;
    int i;
    char **env = envp, *p;

    /* find our data directory. Environment variable takes preference */
    for(env = envp; env && *env; env++) {
        if(!memcmp(*env, "MTSDATA=", 8)) {
            path = (char*)malloc(strlen(*env)+256);
            if(!path) error(lang[ERR_MEM]);
            i = strlen(*env)-9;
            memcpy(path, *env + 8, i+1);
            if(path[i] != DIRSEP) { path[++i] = DIRSEP; i++; }
            strcpy(path + i, "blocks.csv");
            f = fopen(path, "r");
            if(f) { path[i] = 0; fclose(f); }
            else { free(path); path = NULL; }
        }
#ifndef __WIN32__
        if(!memcmp(*env, "HOME=", 5))
            home = *env + 5;
#endif
    }
#ifndef __WIN32__
    /* in user's home */
    if(!path && home) {
        path = (char*)malloc(strlen(home)+256);
        if(!path) error(lang[ERR_MEM]);
        i = strlen(home)-1;
        memcpy(path, home, i + 1);
        if(path[i] != DIRSEP) { path[++i] = DIRSEP; i++; }
        /* Linux and BSDs */
        strcpy(path + i, ".config/mtsedit/blocks.csv");
        f = fopen(path, "r");
        if(f) { path[strlen(path)-10] = 0; fclose(f); }
        else {
            strcpy(path + i, "Library/Application Support/MTSEdit/blocks.csv");
            f = fopen(path, "r");
            if(f) { path[strlen(path)-10] = 0; fclose(f); }
            else { free(path); path = NULL; }
        }
    }
    /* system wide */
    if(!path) {
        path = (char*)malloc(26+256);
        if(!path) error(lang[ERR_MEM]);
        strcpy(path, "/usr/share/mtsedit/blocks.csv");
        f = fopen(path, "r");
        if(f) { path[strlen(path)-10] = 0; fclose(f); }
        else {
            strcpy(path, "/usr/local/share/mtsedit/blocks.csv");
            f = fopen(path, "r");
            if(f) { path[strlen(path)-10] = 0; fclose(f); }
            else {
                strcpy(path, "/Applications/MTSEdit.app/Contents/Resources/data/blocks.csv");
                f = fopen(path, "r");
                if(f) { path[strlen(path)-10] = 0; fclose(f); }
                else { free(path); path = NULL; }
            }
        }
    }
#endif
    /* same directory as the executable */
    if(!path) {
        path = (char*)malloc(strlen(argv[0])+256);
        if(!path) error(lang[ERR_MEM]);
        strcpy(path, argv[0]);
        p = strrchr(path, DIRSEP);
        if(p) p++; else { sprintf(path, ".%c", DIRSEP); p = path + 2; }
        sprintf(p, "data%cblocks.csv", DIRSEP);
        f = fopen(path, "r");
        if(f) { path[strlen(path)-10] = 0; fclose(f); }
        else {
            /* last resort, development repository or /opt/mtsedit/bin, data in parent directory */
            sprintf(p, "..%cdata%cblocks.csv", DIRSEP, DIRSEP);
            f = fopen(path, "r");
            if(f) { path[strlen(path)-10] = 0; fclose(f); }
            else { free(path); path = NULL; }
        }
    }
    if(!path) error(lang[ERR_DATADIR]);
    fn = path + strlen(path);
}

/**
 * Parse the blocks.csv
 */
void blocks_parse()
{
    stbi__context sc;
    stbi__result_info ri;
    int w, h, l, r, g, b, n;
    unsigned int numfiles = 0, numbids = 0, i, j, k, m, *tmp;
    char **files = NULL, *used, c, *s, *e;
    unsigned int size;
    unsigned char *img;
    char *data = (char*)readfile("blocks.csv", &size);
    char *tr = NULL, trname[16], *t, **trl = NULL, *bid = NULL, **bids = NULL;
    FILE *f;

    if(!data || !size) error(lang[ERR_CSV]);

    /* get block images */
    *fn = 0;
    numfiles = listdir(path, &files, 0);
    used = (char*)malloc(numfiles);
    if(!used) error(lang[ERR_MEM]);
    memset(used, 0, numfiles);

    /* read block name dictionary if exists */
    if(!noblocknametranslate) {
        sprintf(trname, "blocks_%c%c.txt", lang[-1][0], lang[-1][1]);
        tr = (char*)readfile(trname, &size);
        if(tr) {
            for(e = tr, i = 0; *e; e++) {
                while(*e == '\r' || *e == '\n' || *e == ' ' || *e == '\t') e++;
                trl = (char**)realloc(trl, (i+2) * sizeof(char*));
                if(!trl) error(lang[ERR_MEM]);
                trl[i++] = e;
                while(*e && *e != '\r' && *e != '\n') e++;
                if(!*e) break;
                *e = 0;
            }
            if(trl && i) trl[i] = NULL;
        }
    }

    /* read numeric Block ID remapping if specified */
    if(remapfile) {
        /* don't use readfile, this file might not be in the data directory */
        f = fopen(remapfile, "rb");
        if(f) {
            fseek(f, 0L, SEEK_END);
            size = (unsigned int)ftell(f);
            fseek(f, 0L, SEEK_SET);
            bid = (char*)malloc(size + 1);
            if(!bid) error(lang[ERR_MEM]);
            fread(bid, size, 1, f);
            bid[size] = 0;
            fclose(f);
            for(e = bid, numbids = 0; *e; e++) {
                while(*e == '\r' || *e == '\n' || *e == ' ' || *e == '\t') e++;
                bids = (char**)realloc(bids, (numbids+1) * sizeof(char*));
                if(!bids) error(lang[ERR_MEM]);
                bids[numbids++] = e;
                while(*e && *e != '\r' && *e != '\n') e++;
                if(!*e) break;
                *e = 0;
            }
        }
    }

    /* parse header */
    for(e = data, i = j = 0; *e && *e != '\r' && *e != '\n' && i < 5; e++) {
        if(*e == '\"') j ^= 1;
        if(!j && (*e == ',' || *e == ';' || *e == '\t')) i++;
    }
    if(i != 5 || !*e || *e == '\r' || *e == '\n') errorcsv(1);
    while(*e && *e != '\r' && *e != '\n') {
        while(*e == ',' || *e == ';' || *e == '\t' || *e == ' ') e++;
        if(*e == '\"') { e++; c = '\"'; } else c = ',';
        for(s = e; *e && *e != '\r' && *e != '\n' && *e != c; e++)
            if(*e == '\\' || (*e == '\"' && e[1] == '\"')) { e++; continue; }
        while(*s <= ' ') s++;
        while(*(e-1) <= ' ') e--;
        j = numpalettes++;
        palettes = (char**)realloc(palettes, numpalettes * sizeof(char*));
        if(!palettes) error(lang[ERR_MEM]);
        if(e <= s) errorcsv(j+1);
        palettes[j] = (char*)malloc(e - s + 1);
        if(!palettes[j]) error(lang[ERR_MEM]);
        memcpy(palettes[j], s, e - s + 1);
        palettes[j][e - s] = 0;
        if((int)(e - s) > lenpalettes) lenpalettes = e - s;
        while(*e && *e != ',' && *e != ';' && *e != '\t' && *e != '\r' && *e != '\n') e++;
    }
    /* add Air as first block */
    numblocks++;
    blocks = (mtsblock_t*)realloc(blocks, numblocks * sizeof(mtsblock_t));
    if(!blocks) error(lang[ERR_MEM]);
    memset(&blocks[0], 0, sizeof(mtsblock_t));
    for(m = 0, t = NULL; trl && trl[m]; m++)
        if(!memcmp(trl[m], "Air=", 4)) { t = trl[m] + 4; break; }
    if(!t) t = lang[AIR];
    blocks[0].name = (char*)malloc(strlen(t)+1);
    if(!blocks[0].name) error(lang[ERR_MEM]);
    strcpy(blocks[0].name, t);
    blocks[0].blocknames = (char**)malloc((numpalettes + 3) * sizeof(char*));
    if(!blocks[0].blocknames) error(lang[ERR_MEM]);
    for(i = 0; i < (unsigned int)numpalettes + 3; i++) {
        blocks[0].blocknames[i] = (char*)malloc(!i ? 33 : 4);
        if(!blocks[0].blocknames[i]) error(lang[ERR_MEM]);
        if(!i)
            memcpy(blocks[0].blocknames[i], "minecraft:air/minecraft:cave_air", 33);
        else
            memcpy(blocks[0].blocknames[i], "air", 4);
    }
    for(i = 0; i < numbids; i++)
        if(!strcmp(bids[i], "Air")) { blocks[0].blockids[0] = i; break; }

    /* parse rows */
    while(*e) {
        if(*e == '\r' || *e == '\n') {
            while(*e == '\r' || *e == '\n') e++;
            if(!*e) break;
            /* get canonical name */
            if(*e == '\"') { e++; c = '\"'; } else c = ',';
            for(s = e; *e && *e != '\r' && *e != '\n' && *e != c; e++)
                if(*e == '\\' || (*e == '\"' && e[1] == '\"')) { e++; continue; }
            while(*s <= ' ') s++;
            while(*(e-1) <= ' ') e--;
            j = numblocks++;
            blocks = (mtsblock_t*)realloc(blocks, numblocks * sizeof(mtsblock_t));
            if(!blocks) error(lang[ERR_MEM]);
            memset(&blocks[j], 0, sizeof(mtsblock_t));
            blocks[j].rotate = 1;
            blocks[j].blocknames = (char**)malloc((numpalettes + 3) * sizeof(char*));
            if(!blocks[j].blocknames) error(lang[ERR_MEM]);
            memset(blocks[j].blocknames, 0, (numpalettes + 3) * sizeof(char*));
            for(k = 0; s + k < e; k++)
                if(s[k] == ' ' || s[k] == '\'') s[k] = '_';
            /* translate block name */
            for(m = 0, t = s; trl && trl[m]; m++)
                if(!memcmp(trl[m], s, k) && trl[m][k] == '=') { t = trl[m] + k + 1; k = strlen(t); break; }
            blocks[j].name = (char*)malloc(k + 1);
            if(!blocks[j].name) error(lang[ERR_MEM]);
            for(i = 0; i < k; i++)
                blocks[j].name[i] = t[i] == '_' ? ' ' : t[i];
            blocks[j].name[i] = 0;
            /* get Block ID remapping if exists */
            for(i = 0; i < numbids; i++)
                if(!bids[i][k] && !memcmp(bids[i], s, k)) { blocks[j].blockids[0] = i; break; }
            /* get block images and possible param2 values */
            for(i = 0, l = 0; i < numfiles; i++) {
                if((int)strlen(files[i]) == (int)(e - s + 4) && !memcmp(files[i], s, e - s)) {
                    used[i] = 1;
                    img = readfile(files[i], &size);
                    if(img && size) {
                        sc.read_from_callbacks = 0;
                        sc.img_buffer = sc.img_buffer_original = img;
                        sc.img_buffer_end = sc.img_buffer_original_end = img + size;
                        ri.bits_per_channel = 8;
                        blocks[j].img = (unsigned char*)stbi__png_load(&sc, &w, &h, &r, 0, &ri);
                        free(img);
                        if(blocks[j].img) {
                            blocks[j].numpar2 = (h >> 5) & 0xFF;
                            if(r != 4) {
                                tmp = (unsigned int *)malloc(w * h * 4);
                                if(!tmp) error(lang[ERR_MEM]);
                                for(m = 0; (int)m < w * h; m++)
                                    switch(r) {
                                        case 1:
                                            tmp[m] = 0xFF000000 | (blocks[j].img[m] << 16) | (blocks[j].img[m] << 8)
                                                | (blocks[j].img[m]);
                                        break;
                                        case 2:
                                            tmp[m] = (blocks[j].img[(m<<1)+1] << 24) | (blocks[j].img[m<<1] << 16) |
                                                (blocks[j].img[m<<1] << 8) | (blocks[j].img[m<<1]);
                                        break;
                                        case 3:
                                            tmp[m] = 0xFF000000 | (blocks[j].img[m*3+2] << 16) | (blocks[j].img[m*3+1] << 8)
                                                | (blocks[j].img[m*3]);
                                        break;
                                    }
                                free(blocks[j].img);
                                blocks[j].img = (unsigned char*)tmp;
                            }
                            for(m = r = g = b = n = 0; (int)m < w * h * 4; m += 4) {
                                if(blocks[j].img[m+3] >= 127) {
                                    r += blocks[j].img[m+0];
                                    g += blocks[j].img[m+1];
                                    b += blocks[j].img[m+2];
                                    n++;
                                }
                            }
                            if(n) {
                                blocks[j].color = 0xFF000000 | (((b/n) & 0xFF) << 16) | (((g/n) & 0xFF) << 8) | ((r/n) & 0xFF);
                                while(!is_color_unique(blocks[j].color)) {
                                    img = (unsigned char*)&blocks[j].color;
                                    if(img[1] < 255) img[1]++;      /* increase green channel first if we can */
                                    else if(img[0] < 255) img[0]++;
                                    else if(img[2] < 255) img[2]++;
                                    else img[3]--;                  /* for full white, decrease opacity as last resort */
                                }
                            } else blocks[j].color = 0xFFFF0000;
                            l++;
                            if(!dx || !dz || (!memcmp(s, "Cobblestone", 11) && s[11] != '_' && s[11] != ' ')) {
                                detcube(32, 32, blocks[j].img);
                                dx = x1 - x0;
                                dz = _y1 - _y0;
                            }
                        }
                    }
                }
            }
            while(*e && *e != ',' && *e != ';' && *e != '\t' && *e != '\r' && *e != '\n') e++;
            if(!l && verbose) { *e = 0; fprintf(stderr, "mtsedit: blocks.csv(%d): %s '%s'\r\n", j+1, lang[ERR_IMG], s); }
            while(*e == ' ') e++;
            if(*e != ',' && *e != ';' && *e != '\t') errorcsv(j+1);
            e++;
            while(*e == ' ') e++;
            /* get Block IDs */
            if(*e == '\"') { e++; c = '\"'; } else c = ',';
            for(s = e; *e && *e != '\r' && *e != '\n' && *e != c; e++)
                if(*e == '\\' || (*e == '\"' && e[1] == '\"')) { e++; continue; }
            while(*s <= ' ') s++;
            for(i = 0; !numbids && s < e && i < 4;) {
                blocks[j].blockids[i] = atoi(s);
                if(blocks[j].blockids[i]) i++;
                while(*s >= '0' && *s <= '9') s++;
                if(*s == '/') s++;
            }
            while(*e && *e != ',' && *e != ';' && *e != '\t' && *e != '\r' && *e != '\n') e++;
            k = 0;
        }
        /* get palette specific names */
        while(*e == ' ') e++;
        if(*e != ',' && *e != ';' && *e != '\t') errorcsv(j+1);
        e++;
        while(*e == ' ') e++;
        if(!*e) errorcsv(j+1);
        if(*e == '\"') { e++; c = '\"'; } else c = ',';
        for(s = e; *e && *e != '\r' && *e != '\n' && *e != c; e++)
            if(*e == '\\' || (*e == '\"' && e[1] == '\"')) { e++; continue; }
        while(*s <= ' ') s++;
        while(*(e-1) <= ' ') e--;
        if(e != s) {
            if(e <= s) errorcsv(j+1);
            blocks[j].blocknames[k] = (char*)malloc(e - s + 1);
            if(!blocks[j].blocknames[k]) error(lang[ERR_MEM]);
            memcpy(blocks[j].blocknames[k], s, e - s + 1);
            blocks[j].blocknames[k][e - s] = 0;
        }
        k++; if(k > (unsigned int)numpalettes + 3) errorcsv(j+2);
        while(*e && *e != ',' && *e != ';' && *e != '\t' && *e != '\r' && *e != '\n') e++;
    }
    if(!numblocks || numblocks > 65535) error(lang[ERR_CSV]);

    /* free temp resources */
    for(i = 0; i < numfiles; i++) {
        if(verbose && !used[i])
            fprintf(stderr, "mtsedit: '%s'?\r\n", files[i]);
        free(files[i]);
    }
    if(tr) free(tr);
    if(trl) free(trl);
    if(bid) free(bid);
    if(bids) free(bids);
    free(files);
    free(used);
    free(data);
}

/**
 * Free resources
 */
void blocks_free()
{
    int i, j;

    for(i = 0; i < numpalettes; i++) free(palettes[i]);
    free(palettes);

    for(i = 0; i < numblocks; i++) {
        free(blocks[i].name);
        if(blocks[i].blocknames) {
            for(j = 0; j < numpalettes + 3; j++)
                if(blocks[i].blocknames[j]) free(blocks[i].blocknames[j]);
            free(blocks[i].blocknames);
        }
        if(blocks[i].img) free(blocks[i].img);
        if(blocks[i].dr) free(blocks[i].dr);
        if(blocks[i].tr) free(blocks[i].tr);
    }
    free(results);
    free(blocks);
}
