/*
    Copyright 2009-2012 Luigi Auriemma

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA

    http://www.gnu.org/licenses/gpl-2.0.txt
*/

// QuickBMS variables-related operations



int get_memory_file(u8 *str) {
    int     ret = 0;    // because -1 is returned for MEMORY_FILE

    // MEMORY_FILE  = -1
    // MEMORY_FILE1 = -1
    // MEMORY_FILE2 = -2

    if(str) {
        ret = myatoi(str + MEMORY_FNAMESZ);
        if(!ret) ret++;
        if((ret < 0) || (ret > MAX_FILES)) {
            fprintf(stderr, "\nError: too big MEMORY_FILE number\n");
            myexit(-1);
        }
        ret = -ret;
    }
    if(ret >= 0) {
        fprintf(stderr, "\nError: the memory file has a positive number\n");
        myexit(-1);
    }
    return(ret);
}



int add_datatype(u8 *str) {
    if(str) {
        if(!stricmp(str, "Long"))       return(TYPE_LONG);
        if(!stricmp(str, "Int"))        return(TYPE_SHORT);
        if(!stricmp(str, "Byte"))       return(TYPE_BYTE);
        if(!stricmp(str, "ThreeByte"))  return(TYPE_THREEBYTE);
        if(!stricmp(str, "String"))     return(TYPE_STRING);
        if(!stricmp(str, "ASize"))      return(TYPE_ASIZE);
        // added by me
        if(stristr(str,  "bits"))       return(TYPE_BITS);
        if(!stricmp(str, "Longlong"))   return(TYPE_LONGLONG);
        //if(!stricmp(str, "Llong"))      return(TYPE_LONGLONG);
        if(!stricmp(str, "Short"))      return(TYPE_SHORT);
        if(!stricmp(str, "Char"))       return(TYPE_BYTE);
        if(!stricmp(str, "dword"))      return(TYPE_LONG);
        if(!stricmp(str, "word"))       return(TYPE_SHORT);
        if(!stricmp(str, "char16"))     return(TYPE_SHORT);
        if(!stricmp(str, "FileName"))   return(TYPE_FILENAME);
        if(!stricmp(str, "BaseName"))   return(TYPE_BASENAME);
        if(!stricmp(str, "FullName"))   return(TYPE_FULLNAME);
        if(!stricmp(str, "Extension"))  return(TYPE_EXTENSION);
        if(!stricmp(str, "FileExt"))    return(TYPE_EXTENSION);
        if(!stricmp(str, "current_folder")) return(TYPE_CURRENT_FOLDER);
        if(!stricmp(str, "file_folder")) return(TYPE_FILE_FOLDER);
        if(!stricmp(str, "input_folder")) return(TYPE_INOUT_FOLDER);
        if(!stricmp(str, "output_folder")) return(TYPE_INOUT_FOLDER);
        if(!stricmp(str, "bms_folder")) return(TYPE_BMS_FOLDER);
        if(!stricmp(str, "Unicode"))    return(TYPE_UNICODE);
        if(!stricmp(str, "UTF-16"))     return(TYPE_UNICODE);
        if(!stricmp(str, "UTF16"))      return(TYPE_UNICODE);
        if(!stricmp(str, "Binary"))     return(TYPE_BINARY);
        if(!stricmp(str, "Line"))       return(TYPE_LINE);
        if(!stricmp(str, "UTF-8"))      return(TYPE_STRING);
        if(!stricmp(str, "UTF8"))       return(TYPE_STRING);
        if(!stricmp(str, "Alloc"))      return(TYPE_ALLOC);
        if(!stricmp(str, "Compressed")) return(TYPE_COMPRESSED);
        if(!stricmp(str, "float"))      return(TYPE_FLOAT);
        if(!stricmp(str, "float32"))    return(TYPE_FLOAT);
        if(!stricmp(str, "double"))     return(TYPE_DOUBLE);
        if(!stricmp(str, "float64"))    return(TYPE_DOUBLE);
        if(!stricmp(str, "double64"))   return(TYPE_DOUBLE);
        if(!stricmp(str, "longdouble")) return(TYPE_LONGDOUBLE);
        if(!stricmp(str, "double96"))   return(TYPE_LONGDOUBLE);
        if(!stricmp(str, "bool"))       return(TYPE_LONG);
        if(!stricmp(str, "void"))       return(TYPE_LONG);
        if(!stricmp(str, "variable"))   return(TYPE_VARIABLE);
        if(!stricmp(str, "variable1"))  return(TYPE_VARIABLE);
        if(!stricmp(str, "variable2"))  return(TYPE_VARIABLE2);
        if(!stricmp(str, "variable3"))  return(TYPE_VARIABLE3);
        if(!stricmp(str, "unreal"))     return(TYPE_VARIABLE2);
        if(!stricmp(str, "variant"))    return(TYPE_VARIANT);
        if(!stricmp(str, "date"))       return(TYPE_TIME);
        if(!stricmp(str, "time"))       return(TYPE_TIME);
        if(!stricmp(str, "time_t"))     return(TYPE_TIME);
        if(!stricmp(str, "time32"))     return(TYPE_TIME);
        if(!stricmp(str, "timestamp"))  return(TYPE_TIME);
        if(!stricmp(str, "date64"))     return(TYPE_TIME64);
        if(!stricmp(str, "time64"))     return(TYPE_TIME64);
        if(!stricmp(str, "timestamp64"))return(TYPE_TIME);
        if(!stricmp(str, "FILETIME"))   return(TYPE_TIME64);
        if(!stricmp(str, "clsid"))      return(TYPE_CLSID);
        if(!stricmp(str, "classid"))    return(TYPE_CLSID);
        if(!stricmp(str, "uuid"))       return(TYPE_CLSID);
        if(!stricmp(str, "ip"))         return(TYPE_IPV4);
        if(!stricmp(str, "ipv4"))       return(TYPE_IPV4);
        if(!stricmp(str, "inet4"))      return(TYPE_IPV4);
        if(!stricmp(str, "ipv6"))       return(TYPE_IPV6);
        if(!stricmp(str, "inet6"))      return(TYPE_IPV6);
        if(!stricmp(str, "asm"))        return(TYPE_ASM);
        if(!stricmp(str, "assembly"))   return(TYPE_ASM);
        if(!stricmp(str, "assembler"))  return(TYPE_ASM);
        if(!stricmp(str, "x86"))        return(TYPE_ASM);

        // ever at the end!
        //if(!stricmp(str, "8"))          return(TYPE_LONGLONG);
        if(!stricmp(str, "4"))          return(TYPE_LONG);
        if(!stricmp(str, "3"))          return(TYPE_THREEBYTE);
        if(!stricmp(str, "2"))          return(TYPE_SHORT);
        if(!stricmp(str, "1"))          return(TYPE_BYTE);
        if(strstr(str,   "64"))         return(TYPE_LONGLONG);
        if(strstr(str,   "32"))         return(TYPE_LONG);
        if(strstr(str,   "24"))         return(TYPE_THREEBYTE);
        if(strstr(str,   "16"))         return(TYPE_SHORT);
        if(strstr(str,   "8"))          return(TYPE_BYTE);
        // nothing must be added here
    }
    fprintf(stderr, "\nError: invalid datatype %s at line %d\n", str, (i32)bms_line_number);
    myexit(-1);
    return(-1);
}



int get_var_from_name(u8 *name, int namelen) {  // a memory_file IS NOT a variable!
    int     i;

    if(!name) return(-1);
    if(namelen < 0) namelen = strlen(name);
    for(i = 0; variable[i].name; i++) {
        if(strlen(variable[i].name) != namelen) continue;

        // the following check is necessary because name may be longer than namelen
        if(!strnicmp(variable[i].name, name, namelen) && !variable[i].name[namelen]) return(i);
        //if(!strnicmp(variable[i].name, name, namelen) && (strlen(variable[i].name) == namelen)) return(i);
    }
    return(-1);
}



// do NOT enable X4 and memory files here or will be visualized an error!
#define GET_VAR_COMMON(X1,X2,X3,X4) \
    if((idx < 0) || (idx >= MAX_VARS)) { \
        fprintf(stderr, "\nError: the variable index is invalid, there is an error in this tool\n"); \
        myexit(-1); \
    } \
    if(variable[idx].isnum) { \
        if(verbose > 0) printf("             <get %s (%d) 0x%"PRIx"\n", variable[idx].name, (i32)idx, variable[idx].value32); \
        /* else if(verbose < 0) printf("               %-10s 0x%08x\n", variable[idx].name, (i32)variable[idx].value32); */ \
        return(X1); \
    } \
    if(variable[idx].value) { \
        if(verbose > 0) printf("             <get %s (%d) \"%s\"\n", variable[idx].name, (i32)idx, variable[idx].value); \
        /* else if(verbose < 0) printf("               %-10s \"%s\"\n", variable[idx].name, variable[idx].value); */ \
        return(X2); \
    } \
    if(variable[idx].name[0] && strnicmp(variable[idx].name, MEMORY_FNAME, MEMORY_FNAMESZ)) { /* "" is for sequential file names */ \
        if(verbose > 0) printf("- variable \"%s\" seems uninitialized, I use its name\n", variable[idx].name); \
        /* else if(verbose < 0) printf("               %-10s \"%s\"\n", variable[idx].name, variable[idx].name); */ \
        /* myexit(-1); */ \
    } \
    if(verbose > 0) printf("             <get %s (%d) \"%s\"\n", variable[idx].name, (i32)idx, variable[idx].name); \
    /* else if(verbose < 0) printf("               %-10s \"%s\"\n", variable[idx].name, variable[idx].name); */ \
    return(X3);



u8 *get_varname(int idx) {
    if((idx < 0) || (idx >= MAX_VARS)) {
        //printf("\nError: the variable index is invalid, there is an error in this tool\n");
        //myexit(-1);
        return("");
    }
    return(variable[idx].name);
}



int get_var32(int idx) {
    GET_VAR_COMMON(
        variable[idx].value32,
        myatoi(variable[idx].value),
        myatoi(variable[idx].name),
        myatoi(memory_file[-get_memory_file(variable[idx].name)].data))
}



u8 *get_var(int idx) {
    GET_VAR_COMMON(
        myitoa(variable[idx].value32),
        variable[idx].value,
        variable[idx].name,
        memory_file[-get_memory_file(variable[idx].name)].data)
}



int get_varsz(int idx) {
    GET_VAR_COMMON(
        sizeof(int),
        strlen(variable[idx].value),
        strlen(variable[idx].name),
        memory_file[-get_memory_file(variable[idx].name)].size)
}



int var_is_a_string(int idx) {
    GET_VAR_COMMON(
        0,
        1,
        1,
        1)
}



int var_is_a_number(int idx) {
    GET_VAR_COMMON(
        1,
        0,
        0,
        0)
}



int var_is_a_memory_file(int idx) {
    GET_VAR_COMMON(
        0,
        0,
        1,  // uhmmm correct?
        1)
}



int var_is_a_constant(int idx) {
    if((idx < 0) || (idx >= MAX_VARS)) {
        fprintf(stderr, "\nError: the variable index is invalid, there is an error in this tool\n");
        myexit(-1);
    }
    if(variable[idx].constant) return(1);
    return(0);
}



// I have chosen -2 because it's negative and is different than -1, a fast solution
int add_varval(int idx, /*u8 *name,*/ u8 *val, int val32, int valsz) {
    if(valsz != -2) {
        if(variable[idx].constant) return(-1); //goto quit_error;
        if(val) {
            strdup_replace(&variable[idx].value, val, valsz, &variable[idx].size);
            variable[idx].isnum   = 0;
        } else {
            variable[idx].value32 = val32;
            variable[idx].isnum   = 1;
        }
    //} else {
        // avoids problems with commands like putvarchr
        // in short if the variable has no value (uninitialized) then it uses the
        // name but if this value gets reallocated then the name will continue
        // to point to the old value causing tons of problems

        // do not enable the following so the user can notice his errors,
        // for example a full script containing only:
        // putvarchr MYVAR 0x1000000 0
        //strdup_replace(&variable[idx].value, name, -1, &variable[idx].size);
    }
    return(0);
}



int add_var(int idx, u8 *name, u8 *val, int val32, int valsz) {
    // do NOT touch valsz, it's a job of strdup_replace
    if((idx < 0) || (idx >= MAX_VARS)) {
        fprintf(stderr, "\nError: the variable index is invalid, there is an error in this tool\n");
        myexit(-1);
    }
    //if((valsz == -2) && !name) name = ""; // specific for the ARGs, only in case of errors in my programming
    // if(valsz < 0) valsz = STRINGSZ;  do NOT do this, valsz is calculated on the length of val
    if(!name) {  // && (idx >= 0)) {
        //name = variable[idx].name; // unused
        if(add_varval(idx, val, val32, valsz) < 0) goto quit_error;
        //goto quit;
    } else {    // used only when the bms file is parsed at the beginning
        if(!stricmp(name, "EXTRCNT") || !stricmp(name, "BytesRead") || !stricmp(name, "NotEOF") || !stricmp(name, "SOF") || !stricmp(name, "EOF")) {
            if(!mex_default) {
                mex_default = 1;    // this avoids to waste cpu for these boring and useless variables
                mex_default_init(0);
            }
        }
        for(idx = 0; variable[idx].name; idx++) {
            if(!stricmp(variable[idx].name, name)) {
                if(add_varval(idx, val, val32, valsz) < 0) goto quit_error;
                goto quit;
            }
        }
        if(idx >= MAX_VARS) {
            fprintf(stderr, "\nError: the BMS script uses more variables than how much supported by this tool\n");
            myexit(-1);
        }
        strdup_replace(&variable[idx].name, name, -1, &variable[idx].size);
        if(add_varval(idx, val, val32, valsz) < 0) goto quit_error;

        if(!variable[idx].name[0]) {        // ""
            variable[idx].constant = 1;     // it's like read-only
        }

        // if this "if" is removed the tool will be a bit slower but will be able to handle completely the script in the example below
        if(myisdigitstr(variable[idx].name)) {  // removes the problem of Log "123.txt" 0 0
        //if(myisdigit(variable[idx].name[0])) {  // number: why only the first byte? because decimal and hex (0x) start all with a decimal number or a '-'
            //strdup_replace(&variable[idx].value, variable[idx].name, -1, &variable[idx].size);
            variable[idx].value32  = myatoi(variable[idx].name);
            variable[idx].isnum    = 1;
            variable[idx].constant = 1;     // it's like read-only

            // there is only one incompatibility with the string-only variables, but it's acceptable for the moment:
            //   set NAME string "mytest"
            //   set NUM long 0x1234
            //   string NAME += NUM
            //   print "%NAME%"
            //   set NUM string "0x12349999999999"
            //   string NAME += NUM
            //   print "%NAME%"
        }
    }
quit:
    if(verbose > 0) {
        if(variable[idx].isnum) {
            printf("             >set %s (%"PRId") to 0x%"PRIx"\n", variable[idx].name, idx, variable[idx].value32);
        } else if(variable[idx].value) {
            printf("             >set %s (%"PRId") to \"%s\"\n", variable[idx].name, idx, variable[idx].value);
        } else {
            printf("             >set %s (%"PRId") to \"%s\"\n", variable[idx].name, idx, variable[idx].name);
        }
    /*} else if(verbose < 0) {
        if(variable[idx].isnum) {
            printf("             >%-10s 0x%"PRIx"\n", variable[idx].name, variable[idx].value32);
        } else if(variable[idx].value) {
            printf("             >%-10s \"%s\"\n", variable[idx].name, variable[idx].value);
        } else {
            printf("             >%-10s \"%s\"\n", variable[idx].name, variable[idx].name);
        } */
    }
    return(idx);
quit_error:
    fprintf(stderr, "\nError: there is something wrong in the BMS, var %d is a constant number\n", (i32)idx);
    myexit(-1);
    return(-1);
}



void quick_var_from_name_check(u8 **ret_key, int *ret_keysz) {
    int     keysz = -1,
            tmp;
    u8      *key,
            *p;

    if(!ret_key || !*ret_key) return;
    if(ret_keysz) keysz = *ret_keysz;
    if(keysz >= 16) return;   // it's useless to make the check for keys over this size
    key = *ret_key;

    tmp = get_var_from_name(key, keysz);
    if(tmp >= 0) {  // variable
        p = get_var(tmp);
        if(p) {
            keysz = get_varsz(tmp);
            key   = p;
        }
    } else if(!strnicmp(key, MEMORY_FNAME, MEMORY_FNAMESZ)) {   // memory_file
        keysz = memory_file[-get_memory_file(key)].size;
        key   = memory_file[-get_memory_file(key)].data;
    }

    if(ret_keysz) *ret_keysz = keysz;
    *ret_key = key;
}



u8 *bad_chars_filter(u8 *s, int len) {
    int     i;

    for(i = 0; i < len; i++) {
        if(!s[i]) break;
        //if(!isprint(s[i])) return("");    // doesn't work on linux
        //if(s[i] == '\r') continue;    // one line only
        //if(s[i] == '\n') continue;    // one line only
        if((s[i] >= ' ') && (s[i] < 0x7f)) continue;
        return("");
    }
    if(s[i]) return("");
    return(s);
}



int verbose_print(int offset, u8 *cmd, int idx, u8 *str, i32 strsz, int num, i32 more) {
    i32     len;
    u8      *name;

    name = get_varname(idx);
    if(!strcmp(name, QUICKBMS_DUMMY)) return(-1);   // no need of stricmp
    if(str) {
        len = strsz;
        if(strsz < 0) len = strlen(str);
        printf(". %"PRIx" %-7s %-10s \"%.*s\" %d\n", offset, cmd, name, len, bad_chars_filter(str, len), more);
        if(strsz > 0) show_dump(4, str, strsz, stdout);
    } else {
        printf(". %"PRIx" %-7s %-10s 0x%"PRIx" %d\n", offset, cmd, name, num, more);
    }
    return(0);
}

