/*
 *	OLDE Display (SH1107) 表示ライブラリ
 */

#include <tk/tkernel.h>
#include <tk/device.h>
#include <tm/tmonitor.h>

#include "fonts/fonts.h"
#include "oled_disp.h"

/* SH1107 情報定義*/
#define	I2C_SADR		(0x3C)	// I2Cターゲット・アドレス

/* コントロールバイト */
#define	CTLB_LAST_CMD		(0x00)	// 最終コマンド
#define CTLB_NEXT_CMD		(0x80)	// 最終コマンドではない
#define	CTLB_CMD		(0x00)	// コマンド
#define	CTLB_RAM		(0x40)	// データ

/* OLED コマンド */
#define	CMD_SET_COLL		(0x00)	// Set Lower column address
#define	CMD_SET_COLH		(0x10)	// Set Higher column address
#define	CMD_DISP_OFF		(0xAE)	// Display OFF
#define CMD_SET_PADR		(0xB0)	// Set page address

/* OLED 情報 */
#define OLED_WIDTH		128
#define	OLED_HEIGHT		128

/* SH1107 初期化コマンド */
LOCAL CONST UB init_cmd[] = {
	0xAE,			// Display OFF
	0x00, 0x10,		// Set column address	= 0
	0xB0,			// Set page address	= 0
	0xDC, 0x00,		// Set start line 	= 0
	0x81, 0x6F,		// Set contrast		= 128 

	0x21,  			// Set Vertical addressing mode

	0xA0,		 	// Set segment remap	= 0
	0xC0,		  	// Set common output scan direction = 0
	0xA4,			// Set Entire Display OFF/ON = 0

	0xA6,			// Set normal display

	0xD3, 0x00,		// Set display offset	= 0

	0xA8, 0x7F,		// Set Multiplex Ration (POR:0x7F)
	0xD5, 0x50,		// Set Display Clock (POR:0x50)
	0xD9, 0x22,		// Set Dis-charge/Pre-charge Period (POR:0x22)
	0xDB, 0x35,		// Set VCOM Deselect Level (POR:0x35)
	0xAD, 0x81,		// Set DC-DC Setting (POR:0x81)
};

/* コマンド送信 */
LOCAL ER oled_snd_cmd(ID dd_i2c, UB cmd)
{
	UB	buf[2];
	ER	err;
	SZ	asz;

	buf[0] = CTLB_LAST_CMD | CTLB_CMD; buf[1] = cmd;
	err = tk_swri_dev(dd_i2c, I2C_SADR, buf, sizeof(buf), &asz);
	return (err<E_OK)?err:E_OK;
}

/* データ送信 */
LOCAL ER oled_snd_data(ID dd_i2c, UB data)
{
	UB	buf[2];
	ER	err;
	SZ	asz;

	buf[0] = CTLB_LAST_CMD | CTLB_RAM; buf[1] = data;
	err = tk_swri_dev(dd_i2c, I2C_SADR, buf, sizeof(buf), &asz);
	return (err<E_OK)?err:E_OK;
}

/* 表示カラム指定 */
LOCAL ER oled_set_col(ID dd_i2c, UB val)
{
	ER	err;

	err = oled_snd_cmd(dd_i2c, CMD_SET_COLL | (val & 0x0F));
	if(err < E_OK) return err;
	err = oled_snd_cmd(dd_i2c, CMD_SET_COLH | (val >> 4));
	return err;	
}

/* 表示ページ指定 */
LOCAL ER oled_set_page(ID dd_i2c, UB val)
{
	ER	err;

	oled_snd_cmd(dd_i2c, CMD_SET_PADR);

	err = oled_snd_cmd(dd_i2c, CMD_SET_PADR | (val & 0x0F));
	return err;
}

/* OLED ライブラリAPI */
/* OLED 初期化 */
EXPORT ER oled_init(ID dd_i2c)
{
	ER	err;
	UB	const *p_cmd = init_cmd;

	for(INT i = 0; i <sizeof(init_cmd); i++) {
		err = oled_snd_cmd(dd_i2c, *p_cmd++);
		if(err < E_OK) break;
	}
	tk_dly_tsk(100);
	return err;
}

/* OLED オン・オフ */
EXPORT ER oled_onoff(ID dd_i2c, UINT onoff)
{
	if(onoff > 1) return E_PAR;
	return oled_snd_cmd(dd_i2c, CMD_DISP_OFF | onoff);
}

/* OLED全画像塗りつぶし */
EXPORT ER oled_fill_screen(ID dd_i2c, UB val)
{
	ER	err;

	for(UINT c = 0; c < OLED_WIDTH; c++) {
		err = oled_set_col(dd_i2c, c);
		if(err < E_OK) return err;

		for(UINT p = 0; p < OLED_HEIGHT/8; p++) {
			err = oled_snd_data(dd_i2c, val);
			if(err < E_OK) return err;
		}
	}
	return err;
}

/* OLED全画像コピー */
EXPORT ER oled_copy_screen(ID dd_i2c, UB *buf)
{
	ER	err;

	for(UINT c = 0; c < OLED_WIDTH; c++) {
		err = oled_set_col(dd_i2c, c);
		if(err < E_OK) return err;

		for(UINT p = 0; p < OLED_HEIGHT/8; p++) {
			err = oled_snd_data(dd_i2c, *buf++);
			if(err < E_OK) return err;
		}	
	}
	return err;
}

/* OLDE画像転送 */
EXPORT ER oled_copy_block(ID dd_i2c, UINT sx, UINT sy, UINT w, UINT h, UB *buf)
{
	ER	err;

	for(UINT c = sx; c < sx+w; c++) {
		err = oled_set_col(dd_i2c, c);
		if(err < E_OK) return err;

		err = oled_set_page(dd_i2c, sy/8);
		if(err < E_OK) return err;

		for(INT i = 0; i < h/8; i++) {
			err = oled_snd_data(dd_i2c, *buf++);
			if(err < E_OK) return err;
		}
	}
	return E_OK;
}

/* フォント表示 */
/* フォントビットマップ回転 */
LOCAL void rot_font(UB *dst, const UB *src, UINT w) {
	for (INT i = 0; i < (w*(w/8)); ++i) {
		dst[i] = 0;
	}

	for (INT y = 0; y < w; ++y) {
		for (INT x = 0; x < w; ++x) {
			// srcのビット位置計算
			INT src_byte_idx = (y * w + x) / 8;
			INT src_bit_idx = (y * w + x) % 8;
			UB bit_val = (src[src_byte_idx] >> (7 - src_bit_idx)) & 1;

			// 90度回転後のビット配置
			INT new_x = w - y -1;
			INT new_y = x;
			INT dst_byte_idx = (new_y * w + new_x) / 8;
			INT dst_bit_idx = (new_y * w + new_x) % 8;

			// dstにビットを設定
			dst[dst_byte_idx] |= (bit_val << (dst_bit_idx));
		}
	}
}

/* フォントビットマップ反転 */
LOCAL void rev_font(UB *src, UB *dst, UINT w) {
	// dst配列の初期化
	for (INT i = 0; i < (w*(w/8)); ++i) {
		dst[i] = 0;
	}
	
	// 各行を左右反転
	for (INT y = 0; y < w; ++y) {
		for (INT x = 0; x < w; ++x) {
			// srcのビット位置計算
			INT src_byte_idx = (y * w + x) / 8;
			INT src_bit_idx = (y * w + x) % 8;
			UB bit_val = (src[src_byte_idx] >> (7 - src_bit_idx)) & 1;
	
			// 左右反転後のビット配置
			INT new_x = w - x - 1;
			INT dst_byte_idx = (y * w + new_x) / 8;
			INT dst_bit_idx = (y * w + new_x) % 8;
	
			// dstにビットを設定
			dst[dst_byte_idx] |= (bit_val << (7 - dst_bit_idx));
		}
	}
}

/* 8bitフォント 一文字表示*/
EXPORT ER oled_fnt8_printc(ID dd_i2c, UINT col, UINT line, UB ch)
{
	static	UB	fdata[8];
	static	UB	rdata[8];
	ER		err;

	if((col>15) || (line>15)) return E_PAR;

	rot_font(fdata, &Font8.table[(ch-32)*8], 8);
	rev_font(fdata, rdata, 8);
	err = oled_copy_block(dd_i2c, col*8, line*8, 8, 8, rdata);

	return err;
}

/* 8bitフォント 文字列表示*/
EXPORT ER oled_fnt8_prints(ID dd_i2c, UINT col, UINT line, UB *str)
{
	ER	err;

	if((col>15) || (line>15)) return E_PAR;

	while(*str) {
		err = oled_fnt8_printc(dd_i2c, col++, line, *str++);
		if(err < E_OK) return err;

		if(col > 15) {
			col = 0; line++;
			if(line > 15) break;	
		}
	}
	return E_OK;
}

/* 24ビットフォント 文字表示*/
EXPORT ER oled_fnt24_printc(ID dd_i2c, UINT col, UINT line, UB ch)
{
	static UB	fdata[72];
	static UB	rdata[72];
	ER		err;

	if((col>5) || (line>5)) return E_PAR;

	rot_font(fdata, &Font24.table[(ch-32)*72], 24);
	rev_font(fdata, rdata, 24);
	
	err = oled_copy_block(dd_i2c, col*24, line*24, 24, 24, rdata);
	return err;
}

/* 24bitフォント 文字列表示*/
EXPORT ER oled_fnt24_prints(ID dd_i2c, UINT col, UINT line, UB *str)
{
	ER	err;

	if((col>4) || (line>4)) return E_PAR;

	while(*str) {
		err = oled_fnt24_printc(dd_i2c, col++, line, *str++);
		if(err < E_OK) return err;

		if(col > 4) {
			col = 0; line++;
			if(line > 4) break;	
		}
	}
	return E_OK;
}