#include "mini.h"
#include "jfile.h"
#include <string.h>
#include "lua.h"
#include "gif.h"
#include "gifenc.h"
#include "jail_audio.h"
#include "jshader.h"
#include "log.h"

#include "default_font_gif.h"
#include "default_font_fnt.h"

#define MAX_SURFACES 100
#define MAX_FONTS 5

#define SURF_NOTHING   0
#define SURF_EXTERNAL  1
#define SURF_GENERATED 2

#define DRAWMODE_NORMAL 0
#define DRAWMODE_PATTERN 1
#define DRAWMODE_AND 2
#define DRAWMODE_OR 3
#define DRAWMODE_XOR 4
#define DRAWMODE_NOT 5

#define UPDATE_ALWAYS   0
#define UPDATE_WAIT     1
#define UPDATE_TIMEOUT  2

#ifdef MACOS_BUNDLE
#include <libgen.h>
#endif

struct surface_t {
    char    *name {nullptr};
    uint8_t *p {nullptr};
    uint16_t w, h;
    uint32_t size;
    uint8_t  flags {SURF_NOTHING};
};

struct char_t {
    uint8_t x,y,w,h,base;
};

struct font_t {
    char    *name {nullptr};
    uint8_t surface {0};
    uint8_t spacing {1};
    char_t  chars[256];
};

int         fps=0;

char        window_title[256];
char        config_folder[256];

uint16_t    screen_width = 160;
uint16_t    screen_height = 120;
uint8_t     screen_zoom = 4;
bool        screen_fullscreen = false;
bool        screen_cursor = true;

surface_t   surfaces[MAX_SURFACES];
surface_t   *screen_surface = &surfaces[0];
surface_t   *dest_surface = screen_surface;
surface_t   *source_surface = NULL;
surface_t   *map_surface = NULL;

uint8_t     tile_width = 8;
uint8_t     tile_height = 8;

font_t      fonts[MAX_FONTS];
font_t      *current_font;

char        main_lua_file[200] = "main.lua";
bool        override_ini = false;
//char        lua_files[1024];

#define DEST(x, y) dest_surface->p[x+y*dest_surface->w]
#define SOURCE(x, y) source_surface->p[x+y*source_surface->w]
#define TILES(x, y) map_surface->p[x+y*map_surface->w]
#define CURRENT(x, y) surfaces[i].p[(x)+(y)*surfaces[i].w]

namespace ds {
    int         origin[2] = {0, 0};
    int         clip[4] = {0, 0, screen_width-1, screen_height-1};
    uint8_t     trans = 0;
    uint16_t    fill_pattern = 0b1111111111111111;
    uint8_t     draw_palette[256];
    uint8_t     mode = DRAWMODE_NORMAL;
}

int update_mode = UPDATE_ALWAYS;
int timeout = 0;
uint32_t    last_update = 0;
float       delta_time = 0.0f;

bool should_exit = false;
bool should_quit = false;

SDL_Window *mini_win;
SDL_Renderer *mini_ren;
SDL_Texture *mini_bak;
SDL_Texture *mini_shadertex;
Uint32 windowID;
Uint32 *pixels;
int pitch;

uint32_t palette[256] = { 0xFF1a1c2c, 0xFF5d275d, 0xFFb13e53, 0xFFef7d57, 0xFFffcd75, 0xFFa7f070, 0xFF38b764, 0xFF257179,
                         0xFF29366f, 0xFF3b5dc9, 0xFF41a6f6, 0xFF73eff7, 0xFFf4f4f4, 0xFF94b0c2, 0xFF566c86, 0xFF333c57 };

const bool *keys;
Uint8 key_just_pressed = 0;
static char text_input_buffer[10];
static bool has_text_input = false;

int mouse_x, mouse_y, mouse_wheel;
Uint32 mouse_buttons;
uint8_t mouse_just_pressed = 0;
bool double_click = false;
bool mouse_discard = false;

SDL_Gamepad *gamepad = NULL;
int8_t pad_just_pressed = SDL_GAMEPAD_BUTTON_INVALID;

#define MAX_SOUNDS 50
JA_Music_t *music = NULL;
JA_Sound_t *sounds[MAX_SOUNDS];

int16_t beats, num_beats = 0;

void createNewProject();

void raisewindow() {
    SDL_RaiseWindow(mini_win);
}

char* get_value_from_line(char* line) {
    char* equal_character = strchr(line, '=');
    if (equal_character == NULL) return NULL;
    *equal_character = '\0';
    return ++equal_character;
}

void read_ini() {
    int size;
    FILE *f = file_getfilepointer("game.ini", size); // fopen("game.ini", "r");
    char line[1024];
    if (f == NULL) { log_msg(LOG_FAIL, "No s'ha pogut obrir 'game.ini'\n"); exit(-1); }
    log_msg(LOG_OK, "'game.ini' carregat\n");
    while (fgets(line, sizeof(line), f)) {
        char *value = get_value_from_line(line);
        if (value != NULL) {
            value[strlen(value)-1] = '\0';
            if (strcmp(line, "title") == 0) { strcpy(window_title, value); log_msg(LOG_VERBOSE, "title = %s\n", window_title); }
            else if (strcmp(line, "config") == 0) { strcpy(config_folder, value); log_msg(LOG_VERBOSE, "config = %s\n", config_folder); }
            else if (strcmp(line, "width") == 0) { screen_width = atoi(value); log_msg(LOG_VERBOSE, "screen width = %i\n", screen_width); }
            else if (strcmp(line, "height") == 0) { screen_height = atoi(value); log_msg(LOG_VERBOSE, "screen height = %i\n", screen_height); }
            else if (strcmp(line, "zoom") == 0) { screen_zoom = atoi(value); log_msg(LOG_VERBOSE, "screen zoom = %i\n", screen_zoom); }
            else if (strcmp(line, "fullscreen") == 0) { screen_fullscreen = atoi(value); log_msg(LOG_VERBOSE, "screen sullscreen = %i\n", screen_fullscreen); }
            //else if (strcmp(line, "files") == 0) {
                //lua_files = (char*)malloc(strlen(value));
            //    strcpy(lua_files, value);
            //}
        }
    }
    fclose(f);
    //SDL_Log("'game.ini' carregat!\n");
}


namespace mini
{
    namespace surf
    {
        uint8_t create(int w, int h) {
            unsigned int i = 0;
            while (i<MAX_SURFACES && surfaces[i].p != NULL) ++i;
            if (i==MAX_SURFACES) return 255;
            surfaces[i].name = nullptr;
            surfaces[i].w = w;
            surfaces[i].h = h;
            surfaces[i].size = w*h;
            surfaces[i].p = (uint8_t*)calloc(surfaces[i].size,1);
            surfaces[i].flags = SURF_GENERATED;
            log_msg(LOG_INFO, "Surface %i creada.\n", i);
            return i;
        }

        uint8_t load(uint8_t* buffer, const char* name) {
            // Agafar la pròxima textura lliure
            unsigned int i = 0;
            while (i<MAX_SURFACES && surfaces[i].p != NULL) ++i;
            if (i==MAX_SURFACES) return 255;

            surfaces[i].p = LoadGif(buffer, &surfaces[i].w, &surfaces[i].h);
            surfaces[i].size = surfaces[i].w*surfaces[i].h;
            surfaces[i].name = (char*)malloc(strlen(name)+1);
            strcpy(surfaces[i].name, name);

            log_msg(LOG_INFO, "Buffer '%s' carregat en surface: %i.\n", name, i);

            return i;
        }

        uint8_t load(const char* filename, const bool external) {
            // Si el gif ja s'ha carregat en una textura, tornem eixa textura
            for (unsigned int i=0; i<MAX_SURFACES; ++i)
                if (surfaces[i].name && strcmp(surfaces[i].name, filename)==0) {
                    log_msg(LOG_INFO, "Carrega de '%s' abortada: Reusant: %i.\n", filename, i);
                    return i;
                }

            // Agafar la pròxima textura lliure
            unsigned int i = 0;
            while (i<MAX_SURFACES && surfaces[i].p != NULL) ++i;
            if (i==MAX_SURFACES) return 255;

            surfaces[i].flags = SURF_NOTHING;
            // Carregar l'arxiu de disc
            int size;
            uint8_t *buffer;
            if (external) {
                buffer = (uint8_t*)file_getfilebufferex(filename, size);
                surfaces[i].flags |= SURF_EXTERNAL;
            } else {
                buffer = (uint8_t*)file_getfilebuffer(filename, size);
            }

            // Si no s'ha pogut, petar
            if (!buffer) {
                log_msg(LOG_FAIL, "Error al intentar obrir l'arxiu '%s'\n", filename);
                exit(-1);
                return 255;
            }

            // Finalment, carregar el gif en la surface
            surfaces[i].p = LoadGif(buffer, &surfaces[i].w, &surfaces[i].h);
            surfaces[i].size = surfaces[i].w*surfaces[i].h;
            surfaces[i].name = (char*)malloc(strlen(filename)+1);
            strcpy(surfaces[i].name, filename);
            free(buffer);
            
            log_msg(LOG_INFO, "Arxiu '%s' carregat en surface: %i.\n", filename, i);
            return i;
        }

        void reloadsurfs() {
            for (unsigned int i=0; i<MAX_SURFACES; ++i)
                if (surfaces[i].name) {
                    if (surfaces[i].flags & SURF_GENERATED) {
                        log_msg(LOG_INFO, "Ignorant surface generada %i.\n", i);
                    } else {
                        log_msg(LOG_INFO, "Recarregant de disc surface %i:'%s'.\n", i, surfaces[i].name);

                        int size;
                        uint8_t *buffer;
                        if (surfaces[i].flags & SURF_EXTERNAL) {
                            buffer = (uint8_t*)file_getfilebufferex(surfaces[i].name, size);
                        } else {
                            buffer = (uint8_t*)file_getfilebuffer(surfaces[i].name, size);
                        }

                        // Si no s'ha pogut, petar
                        if (!buffer) {
                            log_msg(LOG_FAIL, "Error al intentar obrir l'arxiu '%s'\n", surfaces[i].name);
                            exit(-1);
                        }

                        surfaces[i].p = LoadGif(buffer, &surfaces[i].w, &surfaces[i].h);
                        surfaces[i].size = surfaces[i].w*surfaces[i].h;
                        free(buffer);
                    }
                }

        }

        void save(uint8_t surface, const char* filename, uint8_t *pal, uint8_t colors)
        {
            gif::write_gif(filename, surfaces[surface].p, surfaces[surface].w, surfaces[surface].h, pal, colors);
        }

        void destroy(uint8_t surface) {
            if (surfaces[surface].name != NULL) free(surfaces[surface].name);
            surfaces[surface].name = NULL;
            if (surfaces[surface].p != NULL) {
                free(surfaces[surface].p);
                log_msg(LOG_INFO, "Surface %i alliberada.\n", surface);
            }
            surfaces[surface].p = NULL;
            surfaces[surface].flags = SURF_NOTHING;
        }

        int width(uint8_t surface) {
            return surfaces[surface].w;
        }

        int height(uint8_t surface) {
            return surfaces[surface].h;
        }

        void cls(uint8_t color) {
            const uint8_t col = ds::draw_palette[color];
            SDL_memset(dest_surface->p, col, dest_surface->size);
        }

        namespace target {
            void recalculate_clip() {
                if (ds::clip[0]<0) ds::clip[0]=0;
                if (ds::clip[1]<0) ds::clip[1]=0;
                if (ds::clip[2]>=dest_surface->w) ds::clip[2]=dest_surface->w-1;
                if (ds::clip[3]>=dest_surface->h) ds::clip[3]=dest_surface->h-1;
            }

            void set(uint8_t surface) {
                dest_surface = &surfaces[surface];
                recalculate_clip();
            }
            uint8_t get() {
                for (unsigned int i=0; i<MAX_SURFACES; ++i) if (dest_surface == &surfaces[i]) return i;
                return 0;
            }
        }

        namespace source {
            void set(uint8_t surface) {
                source_surface = &surfaces[surface];
            }

            uint8_t get() {
                for (unsigned int i=0; i<MAX_SURFACES; ++i) if (source_surface == &surfaces[i]) return i;
                return 0;
            }
        }

        namespace pixel {
            static inline void pset_fast(int x, int y, uint8_t color) {
                if (ds::trans != color) DEST(x, y) = ds::draw_palette[color];
            }

            static inline void pset_bool(int x, int y, uint8_t color) {
                if (ds::trans != color) {
                    switch (ds::mode) {
                        case DRAWMODE_AND:
                            DEST(x, y) = DEST(x, y) & color;
                            break;
                        case DRAWMODE_OR:
                            DEST(x, y) = DEST(x, y) | color;
                            break;
                        case DRAWMODE_XOR:
                            DEST(x, y) = DEST(x, y) ^ color;
                            break;
                        case DRAWMODE_NOT:
                            DEST(x, y) = ~DEST(x, y);
                            break;
                    }
                }
            }

            static inline void pset_pattern(int x, int y, uint8_t color) {
                int pbx = x % 4, pby = y % 4;
                int pb = pbx+pby*4;
                if (ds::fill_pattern & (1 << pb)) if (ds::trans != color) DEST(x, y) = color;
            }

           // Per a les funcions que pinten tot del mateix color
            static inline void direct_pset(int x, int y, uint8_t color) {
                x += ds::origin[0]; y += ds::origin[1];
                if (x < ds::clip[0] || x > ds::clip[2] || y < ds::clip[1] || y > ds::clip[3]) return;
                switch (ds::mode) {
                    case DRAWMODE_NORMAL: pset_fast(x,y,color); break;
                    case DRAWMODE_PATTERN: pset_pattern(x,y,color); break;
                    default: pset_bool(x,y,color); break;
                }
            }

            // Per a les funcions que van canviant de color (surf.pixel i draw.surf, bàsicament)
            static inline void subst_pset(int x, int y, uint8_t color) {
                direct_pset(x,y,ds::draw_palette[color]);
            }

            void set(int x, int y, uint8_t color) {
                subst_pset(x,y,color);
            }

            uint8_t get(int x, int y) {
                if (!source_surface) return 0;
                if (x < 0 || x > (source_surface->w-1) || y < 0 || y > (source_surface->h-1)) return 0;
                return SOURCE(x, y);
            }
        }
    }

    namespace map
    {
        namespace surf {
            void set(uint8_t surface) {
                map_surface = &surfaces[surface];
            }

            uint8_t get()
            {
                for (unsigned int i=0; i<MAX_SURFACES; ++i) if (map_surface == &surfaces[i]) return i;
                return 0;
            }
        }

        void draw() {
            if (!map_surface || !source_surface) return;

            const int tw = tile_width;
            const int th = tile_height;

            int celw = map_surface->w;
            int celh = map_surface->h;

            int celx = 0;
            int cely = 0;

            int ox = ds::origin[0];
            int oy = ds::origin[1];

            int sx = ox;
            int sy = oy;

            // Clipping global rápido
            if (sx + celw * tw < ds::clip[0] ||
                sy + celh * th < ds::clip[1] ||
                sx > ds::clip[2] ||
                sy > ds::clip[3])
                return;

            // Recorte por izquierda
            if (sx < ds::clip[0]) {
                int diff = (ds::clip[0] - sx) / tw;
                celx += diff;
                celw -= diff;
                sx += diff * tw;
            }

            // Recorte por arriba
            if (sy < ds::clip[1]) {
                int diff = (ds::clip[1] - sy) / th;
                cely += diff;
                celh -= diff;
                sy += diff * th;
            }

            // Recorte por derecha
            int max_x = ds::clip[2] - sx;
            if (max_x < celw * tw) celw = max_x / tw + 1;

            // Recorte por abajo
            int max_y = ds::clip[3] - sy;
            if (max_y < celh * th) celh = max_y / th + 1;

            // Ahora sx, sy son relativos al origen y ya están recortados
            for (int y = 0; y < celh; ++y) {
                int ty = sy + y * th;
                for (int x = 0; x < celw; ++x) {
                    uint8_t tile = mget(celx + x, cely + y);
                    if (!tile) continue;
                    int tx = sx + x * tw;
                    tileblit(tile, tx - ox, ty - oy);
                }
            }
        }

        namespace tile {
            uint8_t get(int celx, int cely) {
                if (!map_surface) return 0;
                if (celx < 0 || celx > (map_surface->w-1) || cely < 0 || cely > (map_surface->h-1)) return 0;
                return TILES(celx, cely);
            }

            void set(int celx, int cely, uint8_t snum) {
                if (!map_surface) return;
                if (celx < 0 || celx > (map_surface->w-1) || cely < 0 || cely > (map_surface->h-1)) return;
                TILES(celx, cely) = snum;
            }
        }

        namespace cell {
            uint8_t getw() { return tile_width; }
            uint8_t geth() { return tile_height; }
            
            void set(int w, int h) {
                tile_width = w;
                tile_height = h;
            }
        }

    }

    namespace draw
    {
        // Bresenham Line Algorithm
        void line(int x0, int y0, int x1, int y1, uint8_t color) {
            int		x, y;
            int		dx, dy;
            int		incx, incy;
            int		balance;
            color = ds::draw_palette[color];

            if (x1 >= x0) { dx = x1 - x0; incx = 1; } else { dx = x0 - x1; incx = -1; }
            if (y1 >= y0) { dy = y1 - y0; incy = 1; } else { dy = y0 - y1; incy = -1; }

            x = x0; y = y0;

            if (dx >= dy) {
                dy <<= 1;
                balance = dy - dx;
                dx <<= 1;

                while (x != x1) {
                    direct_pset(x, y, color);
                    if (balance >= 0) { y += incy; balance -= dx; }
                    balance += dy;
                    x += incx;
                }
                direct_pset(x, y, color);
            } else {
                dx <<= 1;
                balance = dx - dy;
                dy <<= 1;

                while (y != y1) {
                    direct_pset(x, y, color);
                    if (balance >= 0) { x += incx; balance -= dy; }
                    balance += dx;
                    y += incy;
                }
                direct_pset(x, y, color);
            }
        }

        void hline(int x0, int y, int x1, uint8_t color) {
            color = ds::draw_palette[color];
            if (x0>x1) { const int tmp=x0;x0=x1;x1=tmp; }
            for (int x=x0; x<=x1; ++x) direct_pset(x, y, color);
        }

        void vline(int x, int y0, int y1, uint8_t color) {
            color = ds::draw_palette[color];
            if (y0>y1) { const int tmp=y0;y0=y1;y1=tmp; }
            for (int y=y0; y<=y1; ++y) direct_pset(x, y, color);
        }

        void rect(int x, int y, int w, int h, uint8_t color) {
            int x1 = w+x-1;
            int y1 = h+y-1;
            hline(x, y, x1, color);
            hline(x, y1, x1, color);
            vline(x, y, y1, color);
            vline(x1, y, y1, color);
        }

        void rectf(int x, int y, int w, int h, uint8_t color) {
            int x1 = w+x-1;
            int y1 = h+y-1;
            for (int i=y; i<=y1; ++i) hline(x, i, x1, color);
        }

        static inline void circ_scanline(int xc, int yc, int x, int y, uint8_t color) {
            direct_pset(xc+x, yc+y, color);
            direct_pset(xc-x, yc+y, color);
            direct_pset(xc+x, yc-y, color);
            direct_pset(xc-x, yc-y, color);
            direct_pset(xc+y, yc+x, color);
            direct_pset(xc-y, yc+x, color);
            direct_pset(xc+y, yc-x, color);
            direct_pset(xc-y, yc-x, color);
        }

        void circ(int x, int y, uint8_t r, uint8_t color) {
            color = ds::draw_palette[color];
            int xi=0, yi=r;
            int d=3-2*r;
            circ_scanline(x, y, xi, yi, color);
            while (yi>=xi++) {
                d += d>0 ? 4*(xi-yi--)+10 : 4*xi+6;
                circ_scanline(x, y, xi, yi, color);
            }
        }

        static inline void circf_scanline(int xc, int yc, int x, int y, uint8_t color) {
            hline(xc-x, yc+y, xc+x, color);
            hline(xc-x, yc-y, xc+x, color);
            hline(xc-y, yc+x, xc+y, color);
            hline(xc-y, yc-x, xc+y, color);
        }

        void circf(int x, int y, uint8_t r, uint8_t color) {
            int xi=0, yi=r;
            int d=3-2*r;
            circf_scanline(x, y, xi, yi, color);
            while (yi>=xi++) {
                d += d>0 ? 4*(xi-yi--)+10 : 4*xi+6;
                circf_scanline(x, y, xi, yi, color);
            }
        }


        void roundrect(int x, int y, int w, int h, uint8_t r, uint8_t color) {
            int xi=0, yi=r;
            int d=3-2*r;

            int xf = w+x-1;
            int yf = h+y-1;
            int x1 = x+r, y1 = y+r;
            int x2 = xf-r, y2 = yf-r;
            hline(x1, y, x2, color);
            hline(x1, yf, x2, color);
            vline(x, y1, y2, color);
            vline(xf, y1, y2, color);

            color = ds::draw_palette[color];
            while (yi>=xi++) {
                d += d>0 ? 4*(xi-yi--)+10 : 4*xi+6;
                direct_pset(x2+xi, y2+yi, color);
                direct_pset(x1-xi, y2+yi, color);
                direct_pset(x2+xi, y1-yi, color);
                direct_pset(x1-xi, y1-yi, color);
                direct_pset(x2+yi, y2+xi, color);
                direct_pset(x1-yi, y2+xi, color);
                direct_pset(x2+yi, y1-xi, color);
                direct_pset(x1-yi, y1-xi, color);
            }
        }

        void roundrectf(int x, int y, int w, int h, uint8_t r, uint8_t color) {
            int xi=0, yi=r;
            int d=3-2*r;

            int xf = w+x-1;
            int yf = h+y-1;
            int x1 = x+r, y1 = y+r;
            int x2 = xf-r, y2 = yf-r;
            for (int i=y1; i<=y2; ++i) hline(x, i, xf, color);

            while (yi>=xi++) {
                d += d>0 ? 4*(xi-yi--)+10 : 4*xi+6;
                hline(x1-xi, y2+yi, x2+xi, color);
                hline(x1-xi, y1-yi, x2+xi, color);
                hline(x1-yi, y2+xi, x2+yi, color);
                hline(x1-yi, y1-xi, x2+yi, color);
            }
        }

        void oval_scanline(int xc, int yc, int x, int y, float xf, float yf, uint8_t color) {
            direct_pset((xc+x)*xf, (yc+y)*yf, color);
            direct_pset((xc-x)*xf, (yc+y)*yf, color);
            direct_pset((xc+x)*xf, (yc-y)*yf, color);
            direct_pset((xc-x)*xf, (yc-y)*yf, color);
            direct_pset((xc+y)*xf, (yc+x)*yf, color);
            direct_pset((xc-y)*xf, (yc+x)*yf, color);
            direct_pset((xc+y)*xf, (yc-x)*yf, color);
            direct_pset((xc-y)*xf, (yc-x)*yf, color);
        }

        void oval(int x0, int y0, int x1, int y1, uint8_t color) {
            color = ds::draw_palette[color];
            int rx = (x1-x0)/2;
            int ry = (y1-y0)/2;
            int r = rx;
            int x = x0 + rx;
            int y = y0 + ry;
            float xf = 1.0f, yf = 1.0f;
            if (rx>=ry) {r=rx;yf=float(ry)/float(rx);} else {r=ry;xf=float(rx)/float(ry);}
            int xi=0, yi=r;
            int d=3-2*r;
            oval_scanline(x, y, xi, yi, xf, yf, color);
            while (yi>=xi++) {
                d += d>0 ? 4*(xi-yi--)+10 : 4*xi+6;
                oval_scanline(x, y, xi, yi, xf, yf, color);
            }
        }

        static inline void ovalf_scanline(int xc, int yc, int x, int y, float xf, float yf, uint8_t color) {
            hline((xc-x)*xf, (yc+y)*yf, (xc+x)*xf, color);
            hline((xc-x)*xf, (yc-y)*yf, (xc+x)*xf, color);
            hline((xc-y)*xf, (yc+x)*yf, (xc+y)*xf, color);
            hline((xc-y)*xf, (yc-x)*yf, (xc+y)*xf, color);
        }

        void ovalf(int x0, int y0, int x1, int y1, uint8_t color) {
            int rx = (x1-x0)/2;
            int ry = (y1-y0)/2;
            int r = rx;
            int x = x0 + rx;
            int y = y0 + ry;
            float xf = 1.0f, yf = 1.0f;
            if (rx>=ry) {r=rx;yf=float(ry)/float(rx);} else {r=ry;xf=float(rx)/float(ry);}
            int xi=0, yi=r;
            int d=3-2*r;
            ovalf_scanline(x, y, xi, yi, xf, yf, color);
            while (yi>=xi++) {
                d += d>0 ? 4*(xi-yi--)+10 : 4*xi+6;
                ovalf_scanline(x, y, xi, yi, xf, yf, color);
            }
        }

        namespace mode {
            void set(uint8_t mode) {
                ds::mode = mode;
            }
        }
    }
}

void initaudio() {
    JA_Init(48000, SDL_AUDIO_S16, 1);
    for (int i=0;i<MAX_SOUNDS;++i) sounds[i] = NULL;
}

void quitaudio() {
    if (music != NULL) JA_DeleteMusic(music);
    for (int i=0;i<MAX_SOUNDS;++i) if (sounds[i]!=NULL) JA_DeleteSound(sounds[i]);
    JA_Quit();
}

int scrw() {
    return screen_width;
}

int scrh() {
    return screen_height;
}





void shader_init(const char* vshader, const char* fshader)
{
    int filesize;
    char *vshaderfile = file_getfilebuffer(vshader, filesize, true);

    char *fshaderfile = nullptr;
    if (fshader) { fshaderfile = file_getfilebuffer(fshader, filesize, true); }
    shader::init(mini_win, mini_shadertex, vshaderfile, fshaderfile);
}

void shader_enable() { shader::enable(); }
void shader_disable() { shader::disable(); }

uint8_t loadfont_from_buffer(const char* buffer, uint8_t index, const char* name) {
    if (!buffer) return false;

    const char* ptr = buffer;
    char line[256];

    // --- Read first line ---
    {
        // Extract line
        int len = 0;
        while (ptr[len] && ptr[len] != '\n' && len < 255) len++;

        memcpy(line, ptr, len);
        line[len] = '\0';
        ptr += (ptr[len] == '\n') ? len + 1 : len; // Advance pointer
        if (len > 0 && line[len - 1] == '\r') line[len - 1] = '\0';

        if (strncmp(line, "bitmap=", 7) != 0) return false; // Parse first line

        fonts[index].surface = loadsurf(line + 7);

        fonts[index].name = (char*)malloc(strlen(name)+1);
        strcpy(fonts[index].name, name);
    }

    // --- Read character lines ---
    while (*ptr) {
        // Extract next line
        int len = 0;
        while (ptr[len] && ptr[len] != '\n' && len < 255) len++;

        memcpy(line, ptr, len);
        line[len] = '\0';
        ptr += (ptr[len] == '\n') ? len + 1 : len; // Advance pointer
        if (len > 0 && line[len - 1] == '\r') line[len - 1] = '\0';

        // Remove comments
        char* hash = strchr(line, '#');
        if (hash) *hash = '\0';

        // Parse
        int code, x, y, w, h, base;
        if (sscanf(line, "%d: %d %d %d %d %d", &code, &x, &y, &w, &h, &base) == 6) {
            if (code >= 0 && code < 256) {
                fonts[index].chars[code] = { (uint8_t)x, (uint8_t)y, (uint8_t)w, (uint8_t)h, (uint8_t)base };
            }
        }
    }
    return index;
}

uint8_t loadfont(const char *filename) {
    // Si la font ja s'ha carregat, tornem eixa font
    for (unsigned int i=0; i<MAX_FONTS; ++i)
        if (fonts[i].name && strcmp(fonts[i].name, filename)==0) {
            log_msg(LOG_INFO, "Carrega de '%s' abortada: Reusant: %i.\n", filename, i);
            return i;
        }

    // Agafar la pròxima textura lliure
    unsigned int i = 0;
    while (i<MAX_FONTS && fonts[i].name != NULL) ++i;
    if (i==MAX_FONTS) return 255;

    // Carregar l'arxiu de disc
    int size;
    char *buffer;
    buffer = file_getfilebuffer(filename, size);

    // Si no s'ha pogut, petar
    if (!buffer) {
        log_msg(LOG_FAIL, "Error al intentar obrir l'arxiu '%s'\n", filename);
        exit(-1);
        return 255;
    }

    loadfont_from_buffer(buffer, i, filename);
    free(buffer);
    
    log_msg(LOG_INFO, "Arxiu '%s' carregat en font: %i.\n", filename, i);
    return i;
}

uint8_t getfont() {
    for (unsigned int i=0; i<MAX_FONTS; ++i) if (current_font == &fonts[i]) return i;
    return 0;
}

void setfont(uint8_t font) {
    current_font = &fonts[font];
}

uint8_t getfontspacing() {
    return current_font->spacing;
}

void setfontspacing(uint8_t spacing) {
    current_font->spacing = spacing;
}

void createDisplay() {
    if (screen_zoom <= 0) screen_zoom = 1;

    const SDL_DisplayMode *dm = SDL_GetDesktopDisplayMode(SDL_GetPrimaryDisplay());
    while (screen_width*screen_zoom > dm->w || screen_height*screen_zoom > dm->h) screen_zoom--;

    mini_win = SDL_CreateWindow(window_title, screen_width*screen_zoom, screen_height*screen_zoom, SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE|(screen_fullscreen?SDL_WINDOW_FULLSCREEN:0));
    windowID = SDL_GetWindowID(mini_win);
    SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
    mini_ren = SDL_CreateRenderer(mini_win, NULL);
    if (screen_cursor) SDL_ShowCursor(); else SDL_HideCursor();
    mini_bak = SDL_CreateTexture(mini_ren, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, screen_width, screen_height);
    SDL_SetTextureScaleMode(mini_bak, SDL_SCALEMODE_NEAREST);
    SDL_PropertiesID props = SDL_GetTextureProperties(mini_bak);
    int real_pixelformat = SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_FORMAT_NUMBER, -1);
    if (real_pixelformat != SDL_PIXELFORMAT_ARGB8888) {
        log_msg(LOG_FAIL, "Pixelformat incorrecte: %i\n", real_pixelformat);
        exit(1);
    }
    mini_shadertex = SDL_CreateTexture(mini_ren, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, screen_width*screen_zoom, screen_height*screen_zoom);
    SDL_SetTextureScaleMode(mini_shadertex, SDL_SCALEMODE_NEAREST);
    shader::init(mini_win, mini_shadertex, nullptr);
    
    log_msg(LOG_OK, "Graphics subsystem initialized\n");
}

void destroyDisplay() {
    SDL_DestroyTexture(mini_shadertex);
    SDL_DestroyTexture(mini_bak);
    SDL_DestroyRenderer(mini_ren);
    SDL_DestroyWindow(mini_win);
}

void initGamePad() {
    int num_joysticks;
    SDL_JoystickID *joysticks = SDL_GetJoysticks(&num_joysticks);
    if (joysticks) {
        for (int i=0; i<num_joysticks; ++i) {
            if (SDL_IsGamepad(joysticks[i])) {
                gamepad = SDL_OpenGamepad(joysticks[i]);
                if (SDL_GamepadConnected(gamepad)) {
                    SDL_SetGamepadEventsEnabled(true);
                    log_msg(LOG_OK, "Gamepad found and initialized");
                    return;
                }
            }
        }
	}
}

int main(int argc,char*argv[]){
    
    #ifdef DEBUG
        SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG);
        log_msg(LOG_UNSALTED, "MINI v%s\n",MINI_VERSION);
    #endif

    // Gestió de arguments
    // ===============================================================
    if (argc>1)
    {
        if (argv[1][0]=='-' && argv[1][1]=='-') {
            const char *command = &argv[1][2];
            if (strcmp(command, "new")==0) {
                createNewProject();
                exit(0);
            } else if (strcmp(command, "version")==0) {
                exit(0);
            }
        } else if (strstr(argv[1], ".lua")!=nullptr) {
            file_setresourcefolder("./");
            file_setsource(SOURCE_FOLDER);
            strcpy(main_lua_file, argv[1]);
            strcpy(window_title, argv[1]);
            override_ini = true;
        } else if (strstr(argv[1], ".jf2")!=nullptr) {
            file_setresourcefilename(argv[1]);
            file_setsource(SOURCE_FILE);
        } else {
            char path[256] = "./";
            strcat(path, argv[1]);
            file_setresourcefolder(path);
        }
    }
    #ifdef MACOS_BUNDLE
        char res_file[255] = "";
        strcpy(res_file, dirname(argv[0]));
        strcat(res_file, "/../Resources/data.jrf");
        file_setresourcefilename(res_file);
    #endif

    while (!should_quit) {
        should_exit=false;

        // READ INI
        if (!override_ini)
        {
            read_ini();
            file_setconfigfolder(config_folder);
            const char *zoom = file_getconfigvalue("zoom");
            if (zoom) screen_zoom=atoi(zoom);
            const char *fullscreen = file_getconfigvalue("fullscreen");
            if (fullscreen) screen_fullscreen=strcmp(fullscreen, "true")==0?true:false;
            const char *cursor = file_getconfigvalue("cursor");
            if (cursor) screen_cursor=strcmp(cursor, "true")?true:false;
            const char *music_enabled = file_getconfigvalue("music");
            if (music_enabled) JA_EnableMusic(strcmp(music_enabled, "true")==0?true:false);
            const char *sound_enabled = file_getconfigvalue("sound");
            if (sound_enabled) JA_EnableSound(strcmp(sound_enabled, "true")==0?true:false);
        }

        SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMEPAD);

        createDisplay();

        initGamePad();

        reinit();
        initaudio();

        uint8_t font_surf = loadsurf(default_font_gif, "default_font");
        surfaces[font_surf].flags |= SURF_GENERATED;
        loadfont_from_buffer((const char*)default_font_fnt, 0, "default_font");
        current_font = &fonts[0];

        mini::lua::init(main_lua_file);
        mini::lua::callbacks::init();

        Uint32 dt=SDL_GetTicks();
        key_just_pressed = 0;
        pad_just_pressed = SDL_GAMEPAD_BUTTON_INVALID;
        mouse_just_pressed = 0;
        mouse_wheel = 0;
        double_click = false;
        has_text_input = false;
        
        int         fps_counter=0;
        uint32_t    fps_timer=0;

        while(!should_exit) {
            if (update_mode==UPDATE_WAIT) SDL_WaitEvent(NULL);
            else if (update_mode==UPDATE_TIMEOUT) SDL_WaitEventTimeout(NULL, timeout);
            SDL_Event mini_eve;
            while(SDL_PollEvent(&mini_eve)) {
                if (mini_eve.type == SDL_EVENT_QUIT) { should_exit=true; should_quit=true; break; }
                if (mini_eve.type == SDL_EVENT_TEXT_INPUT) {
                    SDL_strlcpy(text_input_buffer, mini_eve.text.text, sizeof(text_input_buffer));
                    has_text_input = true;
                }
                if (mini_eve.type == SDL_EVENT_KEY_DOWN) {
#ifdef DEBUG
                    if (mini_eve.key.scancode == SDL_SCANCODE_F12) {
                        reloadsurfs();
                    //} else if (mini_eve.key.scancode == SDL_SCANCODE_F11) {
                    //    mini::lua::debug::toggle();
                    } else if (mini_eve.key.scancode == SDL_SCANCODE_F5) {
                        should_exit=true;
                    } else {
                        key_just_pressed = mini_eve.key.scancode;
                    }
#else
                    key_just_pressed = mini_eve.key.scancode;
#endif
                }
                if (mini_eve.type == SDL_EVENT_MOUSE_BUTTON_UP) {
                    if (mouse_discard)
                        mouse_discard = false;
                    else
                        if (mini_eve.button.clicks==2 && mini_eve.button.button==SDL_BUTTON_LEFT) {
                            double_click = true;
                        } else {
                            mouse_just_pressed = mini_eve.button.button;
                        }
                }
                if (mini_eve.type == SDL_EVENT_MOUSE_WHEEL) {
                    mouse_wheel = mini_eve.wheel.y;
                }
                if (mini_eve.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
                    pad_just_pressed = mini_eve.gbutton.button;
                }
                /*if ( (mini_eve.type == SDL_WINDOWEVENT) &&
                     (mini_eve.window.windowID == windowID) &&
                     (mini_eve.window.event == SDL_WINDOWEVENT_MOVED) ) {
                    windowpos_x = mini_eve.window.data1-4;
                    windowpos_y = mini_eve.window.data2-36;
                    //SDL_GetWindowPosition(mini_win, &windowpos_x, &windowpos_y);
                }*/
            }
            keys = SDL_GetKeyboardState(NULL);
            float real_mouse_x, real_mouse_y;
            mouse_buttons = SDL_GetMouseState(&real_mouse_x, &real_mouse_y);
            float mx, my;
            SDL_RenderCoordinatesFromWindow(mini_ren, real_mouse_x, real_mouse_y, &mx, &my);
            mouse_x = int(mx/screen_zoom);
            mouse_y = int(my/screen_zoom);
            //mouse_x /= screen_zoom; mouse_y /= screen_zoom;
            
            if (SDL_GetTicks()-dt>13) {
                dt = SDL_GetTicks();
                
                if (mini::lua::runnning()) {
                    delta_time = float(SDL_GetTicks() - last_update)/1000.0f;
                    last_update = SDL_GetTicks();

                    mini::lua::callbacks::update();
                } else {
                    exception_loop();
                }
                if (beats==0)beats=num_beats;
                if (beats>0)beats--;
                key_just_pressed = 0;
                mouse_just_pressed = 0;
                has_text_input = false;
                text_input_buffer[0] = '\0';
                double_click = false;
                mouse_wheel = 0;
                pad_just_pressed = SDL_GAMEPAD_BUTTON_INVALID;
            }
            SDL_SetRenderTarget(mini_ren, mini_shadertex);
            SDL_SetRenderDrawColor(mini_ren, 0, 0, 0, 255);
            SDL_RenderClear(mini_ren);
            SDL_LockTexture(mini_bak, NULL, (void**)&pixels, &pitch);
            for (uint32_t i=0;i<screen_surface->size;++i) pixels[i] = palette[screen_surface->p[i]];
            SDL_UnlockTexture(mini_bak);
            SDL_RenderTexture(mini_ren, mini_bak, NULL, NULL); //NEW

            shader::render();
            //SDL_RenderTexture(mini_ren, mini_bak, NULL, NULL);
            //SDL_RenderPresent(mini_ren);

            fps_counter++;
            if (SDL_GetTicks()>=(fps_timer+1000)) {
                fps = fps_counter;
                fps_counter=0;
                fps_timer = SDL_GetTicks();
            }
        }
        mini::lua::quit();
        quitaudio();

        //Mix_Quit();

        for (unsigned int i=0;i<MAX_SURFACES;++i) freesurf(i);
        dest_surface = source_surface = map_surface = NULL;
        destroyDisplay();
        SDL_Quit();
    }
    mini::lua::cleanup();
    return 0;
}


uint32_t *loadpal(const char* filename, uint16_t *palsize) {
    int size;
    uint8_t *buffer = (uint8_t*)file_getfilebuffer(filename, size);
    uint32_t *pal = LoadPalette(buffer, palsize);
    free(buffer);
    return pal;
}

void setpal(uint32_t *pal) {
    for (int i=0; i<256; ++i) palette[i] = pal[i] | 0xff000000;
    //memcpy(palette, pal, 1024);
}

void setcolor(uint8_t index, uint32_t color) {
    palette[index] = color | 0xff000000;
}

uint32_t getcolor(uint8_t index) {
    return palette[index];
}

void settrans(uint8_t index) {
    ds::trans = index;
}

uint8_t gettrans() {
    return ds::trans;
}

uint8_t subpal(uint8_t index, uint8_t color) {
    const uint8_t old = ds::draw_palette[SDL_clamp(index,0,255)];
    ds::draw_palette[SDL_clamp(index,0,255)] = SDL_clamp(color,0,255);
    return old;
}

void reset_subpal() {
    for (int i=0;i<256;++i) ds::draw_palette[i]=i;
}


void fillp(uint16_t pat, bool transparent) {
    ds::fill_pattern = pat;
}

const uint8_t printchar(uint8_t c, int x, int y) {
    char_t &chr = current_font->chars[c];
    blit(chr.x, chr.y, chr.w, chr.h, x, y-chr.base);
    return chr.w;
}

void print(const char* str, int x, int y, uint8_t color) {
    
    const unsigned char* p = (const unsigned char*)str;
    uint8_t cp;
    uint8_t xpos = x;

    uint8_t old_source = getsource();
    setsource(current_font->surface);
    uint8_t old_color = ds::draw_palette[1];
    ds::draw_palette[1] = color;
    uint8_t old_trans = ds::trans;
    ds::trans = 0;
    while (p[0]!=0) {
        if (p[0] < 0x80) {
            cp = p[0];
            p+=1;
        } else if ((p[0] & 0xE0) == 0xC0) {
            cp = (p[0] << 6) | (p[1] & 0x3F);
            p+=2;
        }
        xpos += printchar(cp, xpos, y) + current_font->spacing;
    }
    ds::trans = old_trans;
    ds::draw_palette[1] = old_color;
    setsource(old_source);
}

void clip(int x, int y, int w, int h) {
    ds::clip[0] = x; ds::clip[1] = y; ds::clip[2] = w-x-1; ds::clip[3] = h-y-1;
    recalculate_clip();
}

void clip() {
    ds::clip[0] = ds::clip[1] = 0; ds::clip[2] = dest_surface->w-1; ds::clip[3] = dest_surface->h-1;
    recalculate_clip();
}

void origin(int x, int y) {
    ds::origin[0] = x;
    ds::origin[1] = y;
}

int camx() {
    return ds::origin[0];
}

int camy() {
    return ds::origin[1];
}


static inline void tileblit(uint8_t n, int x, int y) {
    const int tw = tile_width;
    const int th = tile_height;

    const int tiles_per_row = source_surface->w / tw;

    // Coordenadas del tile dentro del spritesheet
    int tx = (n % tiles_per_row) * tw;
    int ty = (n / tiles_per_row) * th;

    int src_y = ty;

    for (int yi = 0; yi < th; ++yi) {
        int src_x = tx;

        for (int xi = 0; xi < tw; ++xi) {
            uint8_t c = sget(src_x, src_y);
            subst_pset(x + xi, y + yi, c);
            src_x++;
        }

        src_y++;
    }
}

void blit(int sx, int sy, int sw, int sh, int dx, int dy, int dw, int dh, bool flip_x, bool flip_y, bool invert) {
    if (dw == 0) dw = sw;
    if (dh == 0) dh = sh;

    // 16.16 fixed point
    int sdx = (sw << 16) / dw;
    int sdy = (sh << 16) / dh;

    if (flip_x) sdx = -sdx;
    if (flip_y) sdy = -sdy;

    int ssx = flip_x ? ((sx + sw - 1) << 16) : (sx << 16);
    int ssy = flip_y ? ((sy + sh - 1) << 16) : (sy << 16);

    int csy = ssy;

    if (!invert) {
        for (int y = dy; y < dy + dh; ++y) {
            int csx = ssx;
            for (int x = dx; x < dx + dw; ++x) {
                uint8_t color = sget(csx >> 16, csy >> 16);
                subst_pset(x, y, color);
                csx += sdx;
            }
            csy += sdy;
        }
    } else {
        for (int y = dy; y < dy + dh; ++y) {
            int csx = ssx;
            for (int x = dx; x < dx + dw; ++x) {
                uint8_t color = sget(csy >> 16, csx >> 16);
                subst_pset(x, y, color);
                csx += sdx;
            }
            csy += sdy;
        }
    }
}

void blit_r(int sx, int sy, int sw, int sh,
            int dx, int dy, int dw, int dh,
            bool flip_x, bool flip_y,
            float angle_deg)
{
    if (dw == 0) dw = sw;
    if (dh == 0) dh = sh;

    // Centro del destino (rectángulo sin rotar)
    float dcx = dx + dw * 0.5f;
    float dcy = dy + dh * 0.5f;

    // Centro del subrectángulo origen
    float scx = sx + sw * 0.5f;
    float scy = sy + sh * 0.5f;

    // Escalado destino -> origen
    float inv_scale_x = float(sw) / float(dw);
    float inv_scale_y = float(sh) / float(dh);

    // Flips integrados en la escala
    if (flip_x) inv_scale_x = -inv_scale_x;
    if (flip_y) inv_scale_y = -inv_scale_y;

    // Ángulo en radianes
    float a  = angle_deg * 3.14159265f / 180.0f;
    float ca = SDL_cosf(a);
    float sa = SDL_sinf(a);

    // --- 1. Bounding box rotado del rectángulo destino ---

    float hx = dw * 0.5f;
    float hy = dh * 0.5f;

    float vx[4] = { -hx,  hx, -hx,  hx };
    float vy[4] = { -hy, -hy,  hy,  hy };

    float min_x =  1e9f, max_x = -1e9f;
    float min_y =  1e9f, max_y = -1e9f;

    for (int i = 0; i < 4; ++i) {
        float rr_x = vx[i] * ca - vy[i] * sa;
        float rr_y = vx[i] * sa + vy[i] * ca;

        float dxp = dcx + rr_x;
        float dyp = dcy + rr_y;

        if (dxp < min_x) min_x = dxp;
        if (dxp > max_x) max_x = dxp;
        if (dyp < min_y) min_y = dyp;
        if (dyp > max_y) max_y = dyp;
    }

    int bb_x0 = (int)SDL_floorf(min_x);
    int bb_x1 = (int)SDL_ceilf (max_x);
    int bb_y0 = (int)SDL_floorf(min_y);
    int bb_y1 = (int)SDL_ceilf (max_y);

    // --- 2. Rotación inversa + escalado + clipping estricto ---

    for (int y = bb_y0; y <= bb_y1; ++y) {
        for (int x = bb_x0; x <= bb_x1; ++x) {

            // Coordenadas relativas al centro destino
            float rx = x - dcx;
            float ry = y - dcy;

            // Rotación inversa
            float ux =  rx * ca + ry * sa;
            float uy = -rx * sa + ry * ca;

            // Escalado destino -> origen (con flips)
            float sxp = scx + ux * inv_scale_x;
            float syp = scy + uy * inv_scale_y;

            // Clipping estricto al subrectángulo origen
            if (sxp < sx || sxp >= sx + sw ||
                syp < sy || syp >= sy + sh)
                continue; // no pintamos nada

            uint8_t color = sget((int)sxp, (int)syp);
            subst_pset(x, y, color);
        }
    }
}


bool btn(uint8_t i) {
    return keys[i];
}

int wbtnp() {
    return key_just_pressed;
}

bool btnp(uint8_t i) {
    if (key_just_pressed == i) {
        key_just_pressed=0;
        return true;
    } else {
        return false;
    }
}

bool anykey() {
    const bool something_pressed = (key_just_pressed != 0) || (pad_just_pressed != -1);
    key_just_pressed=0;
    pad_just_pressed=-1;
    return something_pressed;
}

void textenable(const bool enable) {
    if (enable)
        SDL_StartTextInput(mini_win);
    else
        SDL_StopTextInput(mini_win);
}

const char* textinput() {
    return has_text_input ? text_input_buffer : nullptr;
}

bool pad(int8_t i) {
    if (!gamepad) return false;
    return SDL_GetGamepadButton(gamepad, SDL_GamepadButton(i)) == 1;
}

bool padp(int8_t i) {
    if (pad_just_pressed == i) {
        pad_just_pressed=-1;
        return true;
    } else {
        return false;
    }
}

int wpad() {
    return pad_just_pressed;
}

int mousex() {
    return mouse_x-ds::origin[0];
}

int mousey() {
    return mouse_y-ds::origin[1];
}

int mwheel() {
    return mouse_wheel;
}

bool mbtn(uint8_t i) {
    if (mouse_discard) return false;
    return mouse_buttons & SDL_BUTTON_MASK(i);
}

bool mbtnp(uint8_t i) {
    return mouse_just_pressed == i;
}

bool doubleclick() {
    return double_click;
}

void mdiscard() {
    mouse_discard = true;
}

bool minside(int x, int y, int w, int h) {
    const int mx = mouse_x-ds::origin[0];
    const int my = mouse_y-ds::origin[1];
    return (mx>=x) && (my>=y) && (mx<x+w) && (my<y+h);
}

float time() {
    return float(SDL_GetTicks())/1000.0f;
}

float delta() {
    return delta_time;
}

bool beat(int16_t i) {
    if (i<0) {
        return beats==0;
    } else {
        beats=num_beats=i;
        return false;
    }
}

int getfps() {
    return fps;
}

void playmusic(const char *filename, const int loop) {
    int size;
    char *buffer = file_getfilebuffer(filename, size);
    if (music != NULL) JA_DeleteMusic(music);
    music = JA_LoadMusic((Uint8*)buffer, size);
    JA_PlayMusic(music, loop);
}

void pausemusic() {
    JA_PauseMusic();
}

void resumemusic() {
    JA_ResumeMusic();
}

void stopmusic(const int t) {
    JA_StopMusic();
}

void musicpos(float value)
{
    JA_SetMusicPosition(value);
}

float musicpos()
{
    return JA_GetMusicPosition();
}

void enablemusic(const bool value)
{
    JA_EnableMusic(value);
    file_setconfigvalue("music", value?"true":"false");
}

const bool ismusicenabled()
{
    return JA_IsMusicEnabled();
}


int loadsound(const char *filename) {
    int size;
    char *buffer = file_getfilebuffer(filename, size);
    int i=0;
    while (i<MAX_SOUNDS && sounds[i]!=NULL) i++;
    if (i==MAX_SOUNDS) { i=0; JA_DeleteSound(sounds[i]); }
    sounds[i]=JA_LoadSound((Uint8*)buffer, size);
    return i;
}

void freesound(int soundfile) {
    JA_DeleteSound(sounds[soundfile]);
    sounds[soundfile] = NULL;
}

int playsound(int soundfile, const int volume) {
    const int channel = JA_PlaySound(sounds[soundfile], 0);
    JA_SetSoundVolume(128);
    //Mix_Volume(channel, volume!=-1?volume:MIX_MAX_VOLUME);
    return channel;
}

void stopsound(int soundchannel) {
    JA_StopChannel(soundchannel);
    //Mix_HaltChannel(soundchannel);
}

void enablesound(const bool value)
{
    JA_EnableSound(value);
    file_setconfigvalue("sound", value?"true":"false");
}

const bool issoundenabled()
{
    return JA_IsSoundEnabled();
}

int getzoom() {
    return screen_zoom;
}

void setzoom(const int value) {
    screen_zoom = value;
    destroyDisplay();
    createDisplay();
    char strzoom[3];
    file_setconfigvalue("zoom", SDL_itoa(screen_zoom, strzoom, 10));
}

void setres(const int w, const int h) {
    screen_width = w;
    screen_height = h;
    destroyDisplay();
    createDisplay();
}

bool getfullscreen() {
    return screen_fullscreen;
}

void setfullscreen(const bool value) {
    screen_fullscreen=value;
    destroyDisplay();
    createDisplay();
    file_setconfigvalue("fullscreen", screen_fullscreen?"true":"false");
}

bool getcursor() {
    return screen_cursor;
}

void setcursor(const bool value) {
    screen_cursor=value;
    if (screen_cursor) SDL_ShowCursor(); else SDL_HideCursor();
}

const char* getconfig(const char* key) {
    return file_getconfigvalue(key);
}

void setconfig(const char* key, const char* value) {
    file_setconfigvalue(key, value);
}

const char *configfolder() {
    return file_getconfigfolder();
}

void setupdatemode(const int value, const int t) {
    update_mode = value;
    timeout = t;
}

int getupdatemode() {
    return update_mode;
}

void exit() {
    should_exit = true;
    should_quit = true;
}

void reinit() {
    log_msg(LOG_INFO, "STARTING A SYSTEM REINITIALIZATION\n");
    ds::mode = DRAWMODE_NORMAL;
    ds::origin[0] = ds::origin[1] = 0;
    ds::clip[0] = ds::clip[1] = 0; ds::clip[2] = screen_width-1; ds::clip[3] = screen_height-1;
    ds::trans=0;
    ds::fill_pattern = 0b1111111111111111;
    for (unsigned int i=0; i<MAX_SURFACES; ++i) freesurf(i);
    setdest(newsurf(screen_width, screen_height));
    dest_surface = screen_surface;

    for (int i=0;i<256;++i) ds::draw_palette[i]=i;
}

void createNewProject() {
    if (file_createFolder("data")) {
        log_msg(LOG_OK, "Directori 'data' creat\n");
    } else {
        log_msg(LOG_FAIL, "No s'ha pogut crear la carpeta 'data'\n");
        exit(-1);
    }

    FILE *f = fopen("./data/game.ini", "w");
    if (f) {
        log_msg(LOG_OK, "Arxiu 'data/game.ini' creat\n");
    } else {
        log_msg(LOG_FAIL, "No s'ha pogut crear l'arxiu 'data/game.ini'\n");
        exit(-1);
    }
    fprintf(f, "title=NEW MINI PROJECT\nconfig=newminiproject\nwidth=320\nheight=240\nzoom=2\n");
    fclose(f);

    f = fopen("./data/main.lua", "w");
    if (f) {
        log_msg(LOG_OK, "Arxiu 'data/main.lua' creat\n");
    } else {
        log_msg(LOG_FAIL, "No s'ha pogut crear l'arxiu 'data/main.lua'\n");
        exit(-1);
    }
    fprintf(f, "function mini.init()\n\nend\n\nfunction mini.update()\n    surf.cls(0)\nend\n");
    fclose(f);

    log_msg(LOG_OK, "Projecte nou creat. Ja està fet lo més dificil del jailgame!\n");
}
