/***
The `cam` module.
@module ctr.cam
@usage local cam = require("ctr.cam")
*/

#include <3ds.h>
#include <3ds/types.h>
#include <3ds/svc.h>
#include <3ds/services/cam.h>

#include <sf2d.h>

#include <lua.h>
#include <lauxlib.h>

#include <malloc.h>

#include "texture.h"

/***
Initialize the camera module.
@function init
*/
static int cam_init(lua_State *L) {
	Result ret = camInit();
	if (ret) {
		lua_pushboolean(L, false);
		lua_pushinteger(L, ret);
		return 2;
	}
	
	lua_pushboolean(L, true);
	return 1;
}

/***
Disable the camera module.
@function shutdown
*/
static int cam_shutdown(lua_State *L) {
  camExit();
  
  return 0;
}

/***
Activate a camera.
@function activate
@tparam number camera camera to activate (`SELECT_x`)
*/
static int cam_activate(lua_State *L) {
	u8 cam = luaL_checkinteger(L, 1);
	
	CAMU_Activate(cam);
	
	return 0;
}

/***
Set the exposure of a camera.
@function setExposure
@tparam number camera (`SELECT_x`)
@tparam number exposure (from `-128` to `127`)
*/
static int cam_setExposure(lua_State *L) {
	u8 cam = luaL_checkinteger(L, 1);
	s8 expo = luaL_checkinteger(L, 2);
	
	CAMU_SetExposure(cam, expo);
	
	return 0;
}

/***
Set the white balance of a camera.
@function setWhiteBalance
@tparam number camera (`SELECT_x`)
@tparam number white white balance (`WHITE_BALANCE_x`)
*/
static int cam_setWhiteBalance(lua_State *L) {
	u8 cam = luaL_checkinteger(L, 1);
	u8 bal = luaL_checkinteger(L, 2);
	
	CAMU_SetWhiteBalance(cam, bal);
	
	return 0;
}

/***
Set the sharpness of a camera.
@function setSharpness
@tparam number camera (`SELECT_x`)
@tparam number sharpness from (from `-128` to `127`)
*/
static int cam_setSharpness(lua_State *L) {
	u8 cam = luaL_checkinteger(L, 1);
	s8 sharp = luaL_checkinteger(L, 2);
	
	CAMU_SetSharpness(cam, sharp);
	
	return 0;
}

/***
Set the auto exposure mode of a camera.
@function setAutoExposure
@tparam number camera (`SELECT_x`)
@tparam boolean auto `true` to enable, `false` to disable
*/
static int cam_setAutoExposure(lua_State *L) {
	u8 cam = luaL_checkinteger(L, 1);
	bool expo = lua_toboolean(L, 2);
	
	CAMU_SetAutoExposure(cam, expo);
	
	return 0;
}

/***
Check if the auto exposure mode is enabled for a camera.
@function isAutoExposure
@tparam number camera (`SELECT_x`)
@treturn boolean `true` if enabled, `false` if not
*/
static int cam_isAutoExposure(lua_State *L) {
	u8 cam = luaL_checkinteger(L, 1);
	bool expo = false;
	
	CAMU_IsAutoExposure(&expo, cam);
	
	lua_pushboolean(L, expo);
	return 1;
}

/***
Set the auto white balance mode for a camera.
@function setAutoWhiteBalance
@tparam number camera (`SELECT_x`)
@tparam boolean auto `true` to enable, `false` to disable
*/
static int cam_setAutoWhiteBalance(lua_State *L) {
	u8 cam = luaL_checkinteger(L, 1);
	bool bal = lua_toboolean(L, 2);
	
	CAMU_SetAutoWhiteBalance(cam, bal);
	
	return 0;
}

/***
Check if the auto white balance mode is enabled for a camera.
@function isAutoWhiteBalance
@tparam number camera (`SELECT_x`)
@treturn boolean `true` if enabled, `false` if not
*/
static int cam_isAutoWhiteBalance(lua_State *L) {
	u8 cam = luaL_checkinteger(L, 1);
	bool bal = false;
	
	CAMU_IsAutoWhiteBalance(&bal, cam);
	
	lua_pushboolean(L, bal);
	return 1;
}

/***
Set the contrast on a camera.
@function setContrast
@tparam number camera (`SELECT_x`)
@tparam number contrast (`CONTRAST_x`)
*/
static int cam_setContrast(lua_State *L) {
	u8 cam = luaL_checkinteger(L, 1);
	u8 cont = luaL_checkinteger(L, 2);
	
	CAMU_SetContrast(cam, cont);
	
	return 0;
}

/***
Set the lens correction of a camera.
@function setLensCorrection
@tparam number camera (`SELECT_x`)
@tparam number correction lens correction (`LENS_CORRECTION_x`)
*/
static int cam_setLensCorrection(lua_State *L) {
	u8 cam = luaL_checkinteger(L, 1);
	u8 corr = luaL_checkinteger(L, 2);
	
	CAMU_SetLensCorrection(cam, corr);
	
	return 0;
}

/***
Set the window where the exposure will be check.
@function setAutoExposureWindow
@tparam number x x starting position of the window
@tparam number y y starting position of the window
@tparam number w width of the window
@tparam number h height of the window
*/
static int cam_setAutoExposureWindow(lua_State *L) {
	u8 cam = luaL_checkinteger(L, 1);
	s16 x = luaL_checkinteger(L, 2);
	s16 y = luaL_checkinteger(L, 3);
	s16 w = luaL_checkinteger(L, 4);
	s16 h = luaL_checkinteger(L, 5);
	
	CAMU_SetAutoExposureWindow(cam, x, y, w, h);
	
	return 0;
}

/***
Set the window where the white balance will be check.
@function setAutoWhiteBalanceWindow
@tparam number x x starting position of the window
@tparam number y y starting position of the window
@tparam number w width of the window
@tparam number h height of the window
*/
static int cam_setAutoWhiteBalanceWindow(lua_State *L) {
	u8 cam = luaL_checkinteger(L, 1);
	s16 x = luaL_checkinteger(L, 2);
	s16 y = luaL_checkinteger(L, 3);
	s16 w = luaL_checkinteger(L, 4);
	s16 h = luaL_checkinteger(L, 5);
	
	CAMU_SetAutoWhiteBalanceWindow(cam, x, y, w, h);
	
	return 0;
}

/***
Enable or disable the noise filter on a camera.
@function setNoiseFilter
@tparam number camera (`SELECT_x`)
@tparam boolean filter `true` to enable, `false` to disable
*/
static int cam_setNoiseFilter(lua_State *L) {
	u8 cam = luaL_checkinteger(L, 1);
	bool fil = lua_toboolean(L, 2);
	
	CAMU_SetNoiseFilter(cam, fil);
	
	return 0;
}

/***
Play a shutter sound.
@function playShutterSound
@tparam number sound shutter sound type (`SHUTTER_x`)
*/
static int cam_playShutterSound(lua_State *L) {
	u8 shut = luaL_checkinteger(L, 1);
	
	CAMU_PlayShutterSound(shut);
	
	return 0;
}

/***
Set the size of the base camera image.
@function setSize
@tparam number camera (`SELECT_x`)
@tparam number size (`SIZE_x`)
@tparam number context (`CONTEXT_x`)
*/
static int cam_setSize(lua_State *L) {
	u8 cam = luaL_checkinteger(L, 1);
	u8 size = luaL_checkinteger(L, 2);
	u8 context = luaL_checkinteger(L, 3);
	
	CAMU_SetSize(cam, size, context);
	
	return 0;
}

/***
Set the effect applied on the image.
@function setEffect
@tparam number camera (`SELECT_x`)
@tparam number effect (`EFFECT_x`)
@tparam number context (`CONTEXT_x`)
*/
static int cam_setEffect(lua_State *L) {
	u8 cam = luaL_checkinteger(L, 1);
	u8 effect = luaL_checkinteger(L, 2);
	u8 context = luaL_checkinteger(L, 3);
	
	CAMU_SetEffect(cam, effect, context);
	
	return 0;
}

/***
Set the frame rate of a camera.
@function setFrameRate
@tparam number camera (`SELECT_x`)
@tparam number rate frame rate (`FRAME_RATE_x`)
*/
static int cam_setFrameRate(lua_State *L) {
	u8 cam = luaL_checkinteger(L, 1);
	u8 rate = luaL_checkinteger(L, 2);
	
	CAMU_SetFrameRate(cam, rate);
	
	return 0;
}

/***
Take a picture and put it in a texture.
@function takePicture
@tparam number camera should be PORT_CAM1 if you have only 1 camera activated (`PORT_x`)
@tparam number w width of the picture
@tparam number h height of the picture
@tparam[opt=PLACE_RAM] number place where to put the texture
@treturn texture the texture object
*/
static int cam_takePicture(lua_State *L) {
	u8 cam = luaL_checkinteger(L, 1);
	s16 w = luaL_optinteger(L, 2, 640);
	s16 h = luaL_optinteger(L, 3, 480);
	u32 bufSize = 0;
	
	// Take the actual picture
	CAMU_GetMaxBytes(&bufSize, w, h);
	u8* buf = malloc(bufSize);
	CAMU_SetTransferBytes(cam, bufSize, w, h);
	
	Handle camReceiveEvent = 0;
	
	CAMU_ClearBuffer(cam);
	CAMU_StartCapture(cam);
	CAMU_SetReceiving(&camReceiveEvent, buf, cam, bufSize, (s16)bufSize);
	svcWaitSynchronization(camReceiveEvent, 300000000ULL);
	CAMU_StopCapture(cam);
	
	// Load it in a texture
	u8 place = luaL_optinteger(L, 4, SF2D_PLACE_RAM);
	
	texture_userdata *texture;
	texture = (texture_userdata *)lua_newuserdata(L, sizeof(*texture));
	
	luaL_getmetatable(L, "LTexture");
	lua_setmetatable(L, -2);
	
	texture->texture = sf2d_create_texture_mem_RGBA8(buf, w, h, TEXFMT_RGB565, place);
	sf2d_texture_tile32(texture->texture);
	
	texture->scaleX = 1.0f;
	texture->scaleY = 1.0f;
	texture->blendColor = 0xffffffff;
	
	return 1;
}

// Functions
static const struct luaL_Reg cam_lib[] = {
	{"init",                      cam_init                     },
	{"shutdown",                  cam_shutdown                 },
	{"activate",                  cam_activate                 },
	{"setExposure",               cam_setExposure              },
	{"setWhiteBalance",           cam_setWhiteBalance          },
	{"setSharpness",              cam_setSharpness             },
	{"setAutoExposure",           cam_setAutoExposure          },
	{"isAutoExposure",            cam_isAutoExposure           },
	{"setAutoWhiteBalance",       cam_setAutoWhiteBalance      },
	{"isAutoWhiteBalance",        cam_isAutoWhiteBalance       },
	{"setContrast",               cam_setContrast              },
	{"setLensCorrection",         cam_setLensCorrection        },
	{"setAutoExposureWindow",     cam_setAutoExposureWindow    },
	{"setAutoWhiteBalanceWindow", cam_setAutoWhiteBalanceWindow},
	{"setNoiseFilter",            cam_setNoiseFilter           },
	{"playShutterSound",          cam_playShutterSound         },
	{"setSize",                   cam_setSize                  },
	{"setEffect",                 cam_setEffect                },
	{"setFrameRate",              cam_setFrameRate             },
	{"takePicture",               cam_takePicture              },
	{NULL, NULL}
};

struct { char *name; int value; } cam_constants[] = {
	/***
	First camera activated.
	@field PORT_CAM1
	*/
	{"PORT_CAM1", PORT_CAM1},
	/***
	Second camera activated.
	@field PORT_CAM2
	*/
	{"PORT_CAM2", PORT_CAM2},
	/***
	The two activated camera. Should not be used ATM.
	@field PORT_BOTH
	*/
	{"PORT_BOTH", PORT_BOTH},
	/***
	@field SELECT_NONE
	*/
	{"SELECT_NONE",      SELECT_NONE     },
	/***
	@field SELECT_OUT1
	*/
	{"SELECT_OUT1",      SELECT_OUT1     },
	/***
	@field SELECT_IN1
	*/
	{"SELECT_IN1",       SELECT_IN1      },
	/***
	@field SELECT_OUT2
	*/
	{"SELECT_OUT2",      SELECT_OUT2     },
	/***
	@field SELECT_IN1_OUT1
	*/
	{"SELECT_IN1_OUT1",  SELECT_IN1_OUT1 },
	/***
	@field SELECT_OUT1_OUT2
	*/
	{"SELECT_OUT1_OUT2", SELECT_OUT1_OUT2},
	/***
	@field SELECT_IN1_OUT2
	*/
	{"SELECT_IN1_OUT2",  SELECT_IN1_OUT2 },
	/***
	@field SELECT_ALL
	*/
	{"SELECT_ALL",       SELECT_ALL      },
	/***
	@field CONTEXT_NONE
	*/
	{"CONTEXT_NONE", CONTEXT_NONE},
	/***
	@field CONTEXT_A
	*/
	{"CONTEXT_A",    CONTEXT_A   },
	/***
	@field CONTEXT_B
	*/
	{"CONTEXT_B",    CONTEXT_B   },
	/***
	@field CONTEXT_BOTH
	*/
	{"CONTEXT_BOTH", CONTEXT_BOTH},
	/***
	@field FLIP_NONE
	*/
	{"FLIP_NONE",       FLIP_NONE      },
	/***
	@field FLIP_HORIZONTAL
	*/
	{"FLIP_HORIZONTAL", FLIP_HORIZONTAL},
	/***
	@field FLIP_VERTICAL
	*/
	{"FLIP_VERTICAL",   FLIP_VERTICAL  },
	/***
	@field FLIP_REVERSE
	*/
	{"FLIP_REVERSE",    FLIP_REVERSE   },
	/***
	640x480
	@field SIZE_VGA
	*/
	{"SIZE_VGA",            SIZE_VGA           },
	/***
	320x240
	@field SIZE_QVGA
	*/
	{"SIZE_QVGA",           SIZE_QVGA          },
	/***
	160x120
	@field SIZE_QQVGA
	*/
	{"SIZE_QQVGA",          SIZE_QQVGA         },
	/***
	352x288
	@field SIZE_CIF
	*/
	{"SIZE_CIF",            SIZE_CIF           },
	/***
	176x144
	@field SIZE_QCIF
	*/
	{"SIZE_QCIF",           SIZE_QCIF          },
	/***
	256x192
	@field SIZE_DS_LCD
	*/
	{"SIZE_DS_LCD",         SIZE_DS_LCD        },
	/***
	512x384
	@field SIZE_DS_LCDx4
	*/
	{"SIZE_DS_LCDx4",       SIZE_DS_LCDx4      },
	/***
	400x240
	@field SIZE_CTR_TOP_LCD
	*/
	{"SIZE_CTR_TOP_LCD",    SIZE_CTR_TOP_LCD   },
	/***
	320x240
	@field SIZE_BOTTOM_LCD
	*/
	{"SIZE_CTR_BOTTOM_LCD", SIZE_CTR_BOTTOM_LCD},
	/***
	@field FRAME_RATE_15
	*/
	{"FRAME_RATE_15",       FRAME_RATE_15      },
	/***
	@field FRAME_RATE_15_TO_5
	*/
	{"FRAME_RATE_15_TO_5",  FRAME_RATE_15_TO_5 },
	/***
	@field FRAME_RATE_15_TO_2
	*/
	{"FRAME_RATE_15_To_2",  FRAME_RATE_15_TO_2 },
	/***
	@field FRAME_RATE_10
	*/
	{"FRAME_RATE_10",       FRAME_RATE_10      },
	/***
	@field FRAME_RATE_8_5
	*/
	{"FRAME_RATE_8_5",      FRAME_RATE_8_5     },
	/***
	@field FRAME_RATE_5
	*/
	{"FRAME_RATE_5",        FRAME_RATE_5       },
	/***
	@field FRAME_RATE_20
	*/
	{"FRAME_RATE_20",       FRAME_RATE_20      },
	/***
	@field FRAME_RATE_20_TO_5
	*/
	{"FRAME_RATE_20_TO_5",  FRAME_RATE_20_TO_5 },
	/***
	@field FRAME_RATE_30
	*/
	{"FRAME_RATE_30",       FRAME_RATE_30      },
	/***
	@field FRAME_RATE_TO_5
	*/
	{"FRAME_RATE_30_TO_5",  FRAME_RATE_30_TO_5 },
	/***
	@field FRAME_RATE_15_TO_10
	*/
	{"FRAME_RATE_15_TO_10", FRAME_RATE_15_TO_10},
	/***
	@field FRAME_RATE_20_TO_10
	*/
	{"FRAME_RATE_20_TO_10", FRAME_RATE_20_TO_10},
	/***
	@field FRAME_RATE_30_TO_10
	*/
	{"FRAME_RATE_30_TO_10", FRAME_RATE_30_TO_10},
	/***
	@field WHITe_BALANCE_AUTO
	*/
	{"WHITE_BALANCE_AUTO",  WHITE_BALANCE_AUTO },
	/***
	@field WHITE_BALANCE_3200K
	*/
	{"WHITE_BALANCE_3200K", WHITE_BALANCE_3200K},
	/***
	@field WHITE_BALANCE_4150K
	*/
	{"WHITE_BALANCE_4150K", WHITE_BALANCE_4150K},
	/***
	@field WHITE_BALANCE_5200K
	*/
	{"WHITE_BALANCE_5200K", WHITE_BALANCE_5200K},
	/***
	@field WHITE_BALANCE_6000K
	*/
	{"WHITE_BALANCE_6000K", WHITE_BALANCE_6000K},
	/***
	@field WHITE_BALANCE_7000K
	*/
	{"WHITE_BALANCE_7000K", WHITE_BALANCE_7000K},
	/***
	@field WHITE_BALANCE_TUNGSTEN
	*/
	{"WHITE_BALANCE_TUNGSTEN",                WHITE_BALANCE_TUNGSTEN               },
	/***
	@field WHITE_BALANCE_WHITE_FLUORESCENT_LIGHT
	*/
	{"WHITE_BALANCE_WHITE_FLUORESCENT_LIGHT", WHITE_BALANCE_WHITE_FLUORESCENT_LIGHT},
	/***
	@field WHITE_BALANCE_DAYLIGHT
	*/
	{"WHITE_BALANCE_DAYLIGHT",                WHITE_BALANCE_DAYLIGHT               },
	/***
	@field WHITE_BALANCE_CLOUDY
	*/
	{"WHITE_BALANCE_CLOUDY",                  WHITE_BALANCE_CLOUDY                 },
	/***
	@field WHITE_BALANCE_HORIZON
	*/
	{"WHITE_BALANCE_HORIZON",                 WHITE_BALANCE_HORIZON                },
	/***
	@field WHITE_BALANCE_SHADE
	*/
	{"WHITE_BALANCE_SHADE",                   WHITE_BALANCE_SHADE                  },
	/***
	@field PHOTO_MODE_NORMAL
	*/
	{"PHOTO_MODE_NORMAL",    PHOTO_MODE_NORMAL   },
	/***
	@field PHOTO_MODE_PORTRAIT
	*/
	{"PHOTO_MODE_PORTRAIT",  PHOTO_MODE_PORTRAIT },
	/***
	@field PHOTO_MODE_LANDSCAPE
	*/
	{"PHOTO_MODE_LANDSCAPE", PHOTO_MODE_LANDSCAPE},
	/***
	@field PHOTO_MODE_NIGHTVIEW
	*/
	{"PHOTO_MODE_NIGHTVIEW", PHOTO_MODE_NIGHTVIEW},
	/***
	@field PHOTO_MODE_LETTER
	*/
	{"PHOTO_MODE_LETTER",    PHOTO_MODE_LETTER   },
	/***
	@field EFFECT_NONE
	*/
	{"EFFECT_NONE",     EFFECT_NONE    },
	/***
	@field EFFECT_MONO
	*/
	{"EFFECT_MONO",     EFFECT_MONO    },
	/***
	@field EFFECT_SEPIA
	*/
	{"EFFECT_SEPIA",    EFFECT_SEPIA   },
	/***
	@field EFFECT_NEGATIVE
	*/
	{"EFFECT_NEGATIVE", EFFECT_NEGATIVE},
	/***
	@field EFFECT_NEGAFILM
	*/
	{"EFFECT_NEGAFILM", EFFECT_NEGAFILM},
	/***
	@field EFFECT_SEPIA01
	*/
	{"EFFECT_SEPIA01",  EFFECT_SEPIA01 },
	/***
	@field CONTRAST_01
	*/
	{"CONTRAST_01",     CONTRAST_PATTERN_01},
	/***
	@field CONTRAST_02
	*/
	{"CONTRAST_02",     CONTRAST_PATTERN_02},
	/***
	@field CONTRAST_03
	*/
	{"CONTRAST_03",     CONTRAST_PATTERN_03},
	/***
	@field CONTRAST_04
	*/
	{"CONTRAST_04",     CONTRAST_PATTERN_04},
	/***
	@field CONTRAST_05
	*/
	{"CONTRAST_05",     CONTRAST_PATTERN_05},
	/***
	@field CONTRAST_06
	*/
	{"CONTRAST_06",     CONTRAST_PATTERN_06},
	/***
	@field CONTRAST_07
	*/
	{"CONTRAST_07",     CONTRAST_PATTERN_07},
	/***
	@field CONTRAST_08
	*/
	{"CONTRAST_08",     CONTRAST_PATTERN_08},
	/***
	@field CONTRAST_09
	*/
	{"CONTRAST_09",     CONTRAST_PATTERN_09},
	/***
	@field CONTRAST_10
	*/
	{"CONTRAST_10",     CONTRAST_PATTERN_10},
	/***
	@field CONTRAST_11
	*/
	{"CONTRAST_11",     CONTRAST_PATTERN_11},
	/***
	@field CONTRAST_LOW
	*/
	{"CONTRAST_LOW",    CONTRAST_LOW       },
	/***
	@field CONTRAST_NORMAL
	*/
	{"CONTRAST_NORMAL", CONTRAST_NORMAL    },
	/***
	@field CONTRAST_HIGH
	*/
	{"CONTRAST_HIGH",   CONTRAST_HIGH      },
	/***
	@field LENS_CORRECTION_OFF
	*/
	{"LENS_CORRECTION_OFF",    LENS_CORRECTION_OFF   },
	/***
	@field LENS_CORRECTION_NORMAL
	*/
	{"LENS_CORRECTION_NORMAL", LENS_CORRECTION_NORMAL},
	/***
	@field LENS_CORRECTION_BRIGHT
	*/
	{"LENS_CORRECTION_BRIGHT", LENS_CORRECTION_BRIGHT},
	/***
	@field SHUTTER_NORMAL
	*/
	{"SHUTTER_NORMAL",    SHUTTER_SOUND_TYPE_NORMAL   },
	/***
	@field SHUTTER_MOVIE
	*/
	{"SHUTTER_MOVIE",     SHUTTER_SOUND_TYPE_MOVIE    },
	/***
	@field SHUTTER_MOVIE_END
	*/
	{"SHUTTER_MOVIE_END", SHUTTER_SOUND_TYPE_MOVIE_END},
	{NULL, 0}
};

int luaopen_cam_lib(lua_State *L) {
	luaL_newlib(L, cam_lib);
	
	for (int i = 0; cam_constants[i].name; i++) {
		lua_pushinteger(L, cam_constants[i].value);
		lua_setfield(L, -2, cam_constants[i].name);
	}
	
	return 1;
}

void load_cam_lib(lua_State *L) {
	luaL_requiref(L, "ctr.cam", luaopen_cam_lib, false);
}
