/*
 *  PSX-Tools Bundle Pack
 *  Copyright (C) 2002-2003 Nicolas "Pixel" Noble
 *
 *  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
 */

/* $Id: luapsx.cpp,v 1.10 2004-12-17 11:49:27 pixel Exp $ */

#include <LuaHandle.h>
#include "luapsx.h"
#include "generic.h"

lua_Number alpha_parameter;

void over(Byte * d, Byte R, Byte G, Byte B, Byte A) {
    d[0] = R;
    d[1] = G;
    d[2] = B;
}

void alpha(Byte d[3], Byte R, Byte G, Byte B, Byte A) {
    A = MIN(A, (Byte) 255);
    d[0] = MIN(((int)d[0] * (255 - A) + R * A) >> 8, 255);
    d[1] = MIN(((int)d[1] * (255 - A) + G * A) >> 8, 255);
    d[2] = MIN(((int)d[2] * (255 - A) + B * A) >> 8, 255);
}

void lighten(Byte * d, Byte R, Byte G, Byte B, Byte A) {
    A = MIN(A, (Byte) 255);
    R = ((int)d[0] * (255 - A) + R * A) >> 8;
    G = ((int)d[1] * (255 - A) + G * A) >> 8;
    B = ((int)d[2] * (255 - A) + B * A) >> 8;
    d[0] = MAX(R, d[0]);
    d[1] = MAX(G, d[1]);
    d[2] = MAX(B, d[2]);
}

void darken(Byte * d, Byte R, Byte G, Byte B, Byte A) {
    A = MIN(A, (Byte) 255);
    R = ((int)d[0] * (255 - A) + R * A) >> 8;
    G = ((int)d[1] * (255 - A) + G * A) >> 8;
    B = ((int)d[2] * (255 - A) + B * A) >> 8;
    d[0] = MIN(R, d[0]);
    d[1] = MIN(G, d[1]);
    d[2] = MIN(B, d[2]);
}

void fade(Byte * d, Byte R, Byte G, Byte B, Byte A) {
    A = alpha_parameter * 255;
    alpha(d, R, G, B, A);
}

enum {
    BLIT_OVER = 0,
    BLIT_OVER32,
    BLIT_ALPHA,
    BLIT_LIGHTEN,
    BLIT_LIGHTEN32,
    BLIT_DARKEN,
    BLIT_DARKEN32,
    BLIT_FADE,
};

typedef void psx;

enum psx_functions_t {
    PSX_BSDECODE = 0,
    PSX_BSENCODE,
    PSX_BLIT,
};

struct lua_functypes_t psx_functions[] = {
    { PSX_BSDECODE,	"bsdecode",	3,  3, { LUA_OBJECT, LUA_NUMBER, LUA_NUMBER } },
    { PSX_BSENCODE,	"bsencode", 	3,  5, { LUA_OBJECT, LUA_NUMBER, LUA_NUMBER, LUA_NUMBER, LUA_NUMBER } },
    { PSX_BLIT,		"blit",		9, 10, { LUA_OBJECT, LUA_OBJECT, LUA_NUMBER, LUA_NUMBER, LUA_NUMBER, LUA_NUMBER, LUA_NUMBER, LUA_NUMBER, LUA_NUMBER, LUA_NUMBER } },
    { -1, 0, 0, 0, 0 }
};

class sLua_psx : public Base {
  public:
    DECLARE_FUNCTION(psx, PSX_BSDECODE);
    DECLARE_FUNCTION(psx, PSX_BSENCODE);
    DECLARE_FUNCTION(psx, PSX_BLIT);
  private:
    static int psx_proceed_statics(Lua * L, int n, int caller);
};

void Luapsx::pushstatics(Lua * L) throw (GeneralException ) {
    CHECK_FUNCTIONS(psx);

    PUSH_FUNCTION(psx, PSX_BSDECODE);
    PUSH_FUNCTION(psx, PSX_BSENCODE);
    PUSH_FUNCTION(psx, PSX_BLIT);

    L->push("BLIT_OVER");
    L->push((lua_Number) BLIT_OVER);
    L->settable(LUA_GLOBALSINDEX);    

    L->push("BLIT_OVER32");
    L->push((lua_Number) BLIT_OVER32);
    L->settable(LUA_GLOBALSINDEX);    

    L->push("BLIT_ALPHA");
    L->push((lua_Number) BLIT_ALPHA);
    L->settable(LUA_GLOBALSINDEX);    

    L->push("BLIT_LIGHTEN");
    L->push((lua_Number) BLIT_LIGHTEN);
    L->settable(LUA_GLOBALSINDEX);    

    L->push("BLIT_LIGHTEN32");
    L->push((lua_Number) BLIT_LIGHTEN32);
    L->settable(LUA_GLOBALSINDEX);

    L->push("BLIT_DARKEN");
    L->push((lua_Number) BLIT_DARKEN);
    L->settable(LUA_GLOBALSINDEX);    

    L->push("BLIT_DARKEN32");
    L->push((lua_Number) BLIT_DARKEN32);
    L->settable(LUA_GLOBALSINDEX);    

    L->push("BLIT_FADE");
    L->push((lua_Number) BLIT_FADE);
    L->settable(LUA_GLOBALSINDEX);    
}

int sLua_psx::psx_proceed_statics(Lua * L, int n, int caller) {
    int r = 0;
    
    switch (caller) {
    case PSX_BSDECODE:
	r = 1;
	{
            bs_init();
	    Buffer * b = new Buffer(true);
	    Handle * f = (Handle *) LuaObject::getme(L, 1);
	    int width = L->tonumber(2);
	    int height = L->tonumber(3);
	    Byte * in = (Byte *) malloc(f->GetSize() + 16);
	    Byte * out = (Byte *) malloc(width * height * 3);
	    LuaBuffer lb(b);
	    lb.pushdestruct(L);
	    f->read(in, f->GetSize());
	    bs_decode_rgb24(out, (bs_header_t *) in, width, height, 0);
            b->write(out, width * height * 3);
	    free(out);
	    free(in);
	}
        break;
    case PSX_BSENCODE:
	r = 3;
	{
	    static unsigned short out[0x80000];
            int width, height;
	    Buffer * b = new Buffer(true);
	    LuaBuffer lb(b);
	    lb.pushdestruct(L);
	    Handle * f = (Handle *) LuaObject::getme(L, 1);
	    bs_input_image_t img;
	    width = L->tonumber(2);
	    height = L->tonumber(3);
	    int max_size = 14112, cur_size;
	    int q_scale = 1;
	    if (n >= 4)
		max_size = L->tonumber(4);
	    if (n >= 5)
		q_scale = L->tonumber(5);
	    Byte * in = (Byte *) malloc(f->GetSize());
	    
	    f->read(in, f->GetSize());
	    
            bs_init();
	    
	    for (cur_size = max_size + 1; max_size < cur_size; q_scale++) {
                img.width = width;
                img.height = height;
                img.lpbits = in;
    	        img.top = img.lpbits;
	        img.nextline = img.width * 3;
	        img.bit = 24;
		cur_size = bs_encode((bs_header_t *) out, &img, 2, q_scale, 0);
	    }
            q_scale--;
	    free(in);

            b->write(out, cur_size);
	    
	    L->push((lua_Number) cur_size);
	    L->push((lua_Number) q_scale);
	}
        break;
    case PSX_BLIT:
	r = 0;
	{
	    Buffer * d = (Buffer *) LuaObject::getme(L, 1);
	    Handle * s = (Handle *) LuaObject::getme(L, 2);
	    int dw = L->tonumber(3),
		dh = L->tonumber(4),
		sw = L->tonumber(5),
		sh = L->tonumber(6),
		sx = L->tonumber(7),
		sy = L->tonumber(8),
		bl = L->tonumber(9);
	    int bytes, dstart = 0, dskip = 0, sstart = 0, sskip = 0, i, j;
	    Byte RGB[3], R, G, B, A = 255;
	    void (*op_func)(Byte *, Byte, Byte, Byte, Byte);
	    
	    switch(bl) {
	    case BLIT_OVER:
		bytes = 3;
		op_func = over;
		break;
	    case BLIT_OVER32:
		bytes = 4;
		op_func = over;
		break;
	    case BLIT_ALPHA:
		bytes = 4;
		op_func = alpha;
		break;
	    case BLIT_LIGHTEN:
		bytes = 3;
		op_func = lighten;
		break;
	    case BLIT_LIGHTEN32:
		bytes = 4;
		op_func = lighten;
		break;
	    case BLIT_DARKEN:
		bytes = 3;
		op_func = darken;
		break;
	    case BLIT_DARKEN32:
		bytes = 4;
		op_func = darken;
		break;
            case BLIT_FADE:
                bytes = 3;
                op_func = fade;
                if (n >= 10)
                    alpha_parameter = L->tonumber(10);
                break;
	    default:
		L->error("Blitting operation unknown.");
		return 0;
	    }

            if ((sx + sw) < 0)
                return 0;

            if (sx >= dw)
                return 0;

            if ((sy + sh) < 0)
                return 0;

            if (sy >= dh)
                return 0;

	    if (sy < 0) {
		sstart -= sw * bytes * sy;
		sh += sy;
		sy = 0;
	    } else {
		dstart += dw * 3 * sy;
	    }
	    
	    if ((sy + sh) > dh) {
		sh -= (sy + sh) - dh;
	    }
	    
	    if (sx < 0) {
		sstart -= sx * bytes;
		sskip -= sx * bytes;
		sw += sx;
		sx = 0;
	    } else {
		dstart += sx * 3;
	    }
	    
	    if ((sx + sw) > dw) {
		sskip += (sx + sw) - dw;
		sw -= (sx + sw) - dw;
	    }
	    
	    dskip = (dw - sw) * 3;
	    
	    d->seek(dstart);
            d->wseek(dstart);
	    s->seek(sstart);
	    
	    for (i = 0; i < sh; i++, s->seek(sskip, SEEK_CUR), d->seek(dskip, SEEK_CUR), d->wseek(dskip, SEEK_CUR)) {
		for (j = 0; j < sw; j++) {
		    RGB[0] = d->readU8();
		    RGB[1] = d->readU8();
		    RGB[2] = d->readU8();
		    R = s->readU8();
		    G = s->readU8();
		    B = s->readU8();
		    if (bytes == 4)
			A = s->readU8();
		    op_func(RGB, R, G, B, A);
		    d->writeU8(RGB[0]);
		    d->writeU8(RGB[1]);
		    d->writeU8(RGB[2]);
		}
	    }
	}
    }
    return r;
}