// =====================================================================
// otenki.c
// 各地域のマーカー部分に今日／明日の天気アイコンを表示する
// =====================================================================

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

#include <GL/gl.h>
#include <GL/glut.h>
#include <AR/gsub.h>
#include <AR/video.h>
#include <AR/param.h>
#include <AR/ar.h>

#include <curl/curl.h>

// HTTP接続のプロキシサーバー 例: "proxy.server.name:8080"
#define PROXY_SERVER ""

// マーカーの数
#define	MARKER_NUM 6
// マーカーの幅(単位:ミリ)
#define	MARKER_SIZE 80.0

// タイトルテクスチャの数
#define TEXTURE_TITLE_NUM 2
// 配列変数textureの、タイトルテクスチャのインデックス
#define TEXTURE_TITLE_TODAY 0
#define TEXTURE_TITLE_TOMORROW 1
// 配列変数textureの、天気アイコンテクスチャの開始インデックス
#define TEXTURE_WEATHERICON_OFFSET 2
// 天気アイコンの数
#define TEXTURE_WEATHERICON_NUM 31
// テクスチャの合計数
#define TEXTURE_TOTAL_NUM (TEXTURE_TITLE_NUM + TEXTURE_WEATHERICON_NUM)

// テクスチャの高さと幅
#define TEXTURE_W 128
#define TEXTURE_H 128

// 天気表示切替変数dispmodeに格納する定数
#define WEATHER_TODAY 0
#define WEATHER_TOMORROW 1

// テクスチャ番号の配列
GLuint	texture[TEXTURE_TOTAL_NUM];
// curlハンドル
CURL *curl = NULL;
// アイコン表示モード (今日/明日切替)
int dispmode = WEATHER_TODAY;
// 都市コード (0は都市ではなくタイトル)
int citycode[] = { 0, 4, 25, 63, 81, 110 };

typedef struct {
  int		patt_id;
  double	width;
  double	center[2];
  double	trans[3][4];
  int		tex_today;
  int		tex_tomorrow;
} OBJECT_T;
OBJECT_T	object[MARKER_NUM + 1];

char	*vconf = "Data/WDM_camera_flipV.xml";
int		xsize, ysize;
int		thresh = 100;
int		count = 0;
char	*cparam_name = "Data/camera_para.dat";
ARParam	cparam;

static void init(void);
static void initMarker(void);
static void initTexture(void);
static void cleanup(void);
static void mainLoop(void);
static void draw(OBJECT_T obj);
static void keyEvent(unsigned char key, int x, int y);

static void loadTextureFromBmp(GLubyte pixels[TEXTURE_W][TEXTURE_H][4], char *filename);
static int getWeatherCode(int city, int type);
static size_t writeXmlToFile(void *ptr, size_t size, size_t nmemb, FILE *stream);

// ■エントリポイント
int main(int argc, char **argv)
{
	glutInit(&argc, argv);
	init();
	initMarker();
	initTexture();

	arVideoCapStart();
	argMainLoop( NULL, keyEvent, mainLoop );
	return (0);
}

// ■初期化処理
static void init(void)
{
	ARParam	wparam;

	if( arVideoOpen( vconf ) < 0 ) exit(EXIT_FAILURE);
	if( arVideoInqSize(&xsize, &ysize) < 0 ) exit(EXIT_FAILURE);
	printf("Image size (x,y) = (%d,%d)\n", xsize, ysize);

	if( arParamLoad(cparam_name, 1, &wparam) < 0 ) {
		printf("Camera parameter load error !!\n");
		exit(EXIT_FAILURE);
	}
	arParamChangeSize( &wparam, xsize, ysize, &cparam );
	arInitCparam( &cparam );
	printf("*** Camera Parameter ***\n");
	arParamDisp( &cparam );

	argInit( &cparam, 1.0, 0, 0, 0, 0 );
}

// ■マーカーの初期化
static void initMarker(void)
{
	int i;
	char filename[MAX_PATH];

	// マーカー読み込み
	for( i = 0; i < MARKER_NUM; i++ ) {
		sprintf(filename, "Data/marker%03d.patt", i);

		if( (object[i].patt_id = arLoadPatt(filename)) < 0 ) {
			printf("Load pattern failed: %s\n", filename);
			cleanup();
			exit(EXIT_FAILURE);
		}
		printf("Loaded pattern: %s\n", filename);

		object[i].width = MARKER_SIZE;
		object[i].center[0] = 0.0;
		object[i].center[1] = 0.0;

		if (citycode[i] != 0) {
			// 都市コードあり、今日明日の天気コードを取得
			if ((object[i].tex_today = getWeatherCode(citycode[i], WEATHER_TODAY)) < 0) {
				printf("Failed getting today's weather code: city=%d\n", citycode[i]);
				object[i].tex_today = 0;
			}
			if ((object[i].tex_tomorrow = getWeatherCode(citycode[i], WEATHER_TOMORROW)) < 0) {
				printf("Failed getting tomorrow's weather code: city=%d\n", citycode[i]);
				object[i].tex_tomorrow = 0;
			}
		}
	}
}


// ■テクスチャの初期化
static void initTexture(void)
{
	int	i;
	GLubyte	pixels[TEXTURE_W][TEXTURE_H][4];
	char filename[MAX_PATH];

	glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
	glGenTextures(TEXTURE_TOTAL_NUM, texture);

	// TEXTURE0: きょうの天気のタイトル
	loadTextureFromBmp(pixels, "Data/title_today.bmp");
	glBindTexture( GL_TEXTURE_2D, texture[TEXTURE_TITLE_TODAY] );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, TEXTURE_W, TEXTURE_H, 0,
				  GL_RGBA, GL_UNSIGNED_BYTE, pixels );

	// TEXTURE1: あしたの天気のタイトル
	loadTextureFromBmp(pixels, "Data/title_tomorrow.bmp");
	glBindTexture( GL_TEXTURE_2D, texture[TEXTURE_TITLE_TOMORROW] );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, TEXTURE_W, TEXTURE_H, 0,
				  GL_RGBA, GL_UNSIGNED_BYTE, pixels );

	// TEXTURE2以降: 天気アイコン
	for( i = 0; i < TEXTURE_WEATHERICON_NUM; i++ ) {
		sprintf(filename, "Data/weather%03d.bmp", i);
		loadTextureFromBmp(pixels, filename);
		glBindTexture( GL_TEXTURE_2D, texture[i + TEXTURE_WEATHERICON_OFFSET] );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
		glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, TEXTURE_W, TEXTURE_H, 0,
					  GL_RGBA, GL_UNSIGNED_BYTE, pixels );
	}
}

// ■終了処理
static void cleanup(void)
{
	arVideoCapStop();
	arVideoClose();
	argCleanup();

	if( curl != NULL ) {
		curl_easy_cleanup(curl);
		curl = NULL;
	}
}

// ■メインループ
static void mainLoop(void)
{
	int				i, j, k;
	ARUint8			*dataPtr;
	ARMarkerInfo	*marker_info;
	int				marker_num;

	// カメラ画像を取得
	if( (dataPtr = (ARUint8 *)arVideoGetImage()) == NULL ) {
		arUtilSleep(2);
		return;
	}
	if( count == 0 ) arUtilTimerReset();
	count++;

	argDrawMode2D();
	argDispImage( dataPtr, 0, 0 );

	// 画像内のマーカー検知
	if( arDetectMarker(dataPtr, thresh, &marker_info, &marker_num) < 0 ) {
		cleanup();
		exit(EXIT_FAILURE);
	}
	arVideoCapNext();

	for( i = 0; i < MARKER_NUM; i++ ) {
		k = -1;
		for( j = 0; j < marker_num; j++ ) {
			if( object[i].patt_id == marker_info[j].id ) {
				if( k == -1 ) k = j;
				else if( marker_info[k].cf < marker_info[j].cf ) k = j;
			}
		}

		if( k != -1 ) {
			// マーカーを検出した！
			arGetTransMat(&marker_info[k], object[i].center, object[i].width, object[i].trans);
			draw(object[i]);
		}
	}

	argSwapBuffers();
}

// ■マーカー検出時の描画処理
static void draw(OBJECT_T obj)
{
	double	gl_para[16];
	GLuint textureId;

	argDrawMode3D();
	argDraw3dCamera(0, 0);

	glEnable(GL_TEXTURE_2D);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

	argConvGlpara(obj.trans, gl_para);
	glMatrixMode(GL_MODELVIEW);
	glLoadMatrixd(gl_para);

	if( obj.patt_id == 0 ) {
		// タイトルマーカー
		textureId = (dispmode == WEATHER_TODAY)?
			texture[TEXTURE_TITLE_TODAY]
			: texture[TEXTURE_TITLE_TOMORROW];
	}
	else {
		// 都市マーカー
		textureId = (dispmode == WEATHER_TODAY)?
			texture[obj.tex_today + TEXTURE_WEATHERICON_OFFSET]
			: texture[obj.tex_tomorrow + TEXTURE_WEATHERICON_OFFSET];
	}

	// テクスチャ描画
	glTranslatef(-50.0, -50.0, 0.0);
	glBindTexture(GL_TEXTURE_2D, textureId);
	glBegin(GL_POLYGON);
		glTexCoord2d(0.0, 0.0); glVertex3f(0.0, 0.0, 0.0);
		glTexCoord2d(1.0, 0.0); glVertex3f(100.0, 0.0, 0.0);
		glTexCoord2d(1.0, 1.0); glVertex3f(100.0, 100.0, 0.0);
		glTexCoord2d(0.0, 1.0); glVertex3f(0.0, 100.0, 0.0);
	glEnd();
}

// ■キーイベント発生時にコールされる関数
static void keyEvent(unsigned char key, int x, int y)
{
	switch( key ) {
		case 0x1b: // ESCキー(0x1b): 終了
			printf("*** %f (frame/sec)\n", (double)count/arUtilTimer());
			cleanup();
			exit(EXIT_SUCCESS);

		case 't': // Tキー(0x74): 今日明日の表示切替
			if( dispmode == WEATHER_TODAY ) {
				dispmode = WEATHER_TOMORROW;
				printf("*** display mode: tomorrow\n");
			}
			else {
				dispmode = WEATHER_TODAY;
				printf("*** display mode: today\n");
			}
			break;
		default:
			break;
	}
}

// ■指定した地域の天気コードを取得
static int getWeatherCode(int city, int type) {
	CURLcode res;
	FILE *fp, *fpRead;
	long statusCode = -1;
	char lineBuf[1024];
	char url[1024];
	char filename[_MAX_PATH];
	int weatherCode = -1;

	// 天気情報のRSSページをファイルに保存

	sprintf(filename, "#weather.%d.%d.xml", city, type);
	if ((fp = fopen(filename, "w")) == NULL) {
		printf("fopen failed.\n");
		return -1;
	}

	if( curl == NULL ) {
		if( (curl = curl_easy_init()) == NULL ) {
			printf("curl_easy_init failed.\n");
			fclose(fp);
			return -1;
		}
	}

	if( 0 < strlen(PROXY_SERVER) ) {
		curl_easy_setopt(curl, CURLOPT_PROXY, PROXY_SERVER);
	}

	if( type == WEATHER_TODAY ) {
		sprintf(url, "http://weather.livedoor.com/forecast/webservice/rest/v1?city=%d&day=today", city);
	}
	else {
		sprintf(url, "http://weather.livedoor.com/forecast/webservice/rest/v1?city=%d&day=tomorrow", city);
	}
	curl_easy_setopt(curl, CURLOPT_URL, url);
	curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeXmlToFile);
	if ((res = curl_easy_perform(curl)) != CURLE_OK) {
		printf("curl_easy_perform failed. res=%d\n", res);
		fclose(fp);
		return -1;
	}
	fclose(fp);

	curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &statusCode);

	// ダウンロードしたファイルを読みこみ、天気コードを取得

	if ((fpRead = fopen(filename, "r")) == NULL) {
		printf("fopen failed.\n");
		return -1;
	}

	// XMLから簡易的に要素を取り出し
	while (fgets(lineBuf, sizeof(lineBuf), fpRead) != NULL) {
		if (strncmp(lineBuf, "<url>http://image.weather.livedoor.com/img/icon/", 48) == 0) {
			sscanf(lineBuf, "<url>http://image.weather.livedoor.com/img/icon/%d.gif</url>", &weatherCode);
		}
	}
	fclose(fpRead);

	printf("getWeatherCode: city=%d status=%d code=%d\n", city, statusCode, weatherCode);

	return weatherCode;
}

// ■ビットマップファイルからテクスチャデータ読み込み
static void loadTextureFromBmp(GLubyte pixels[TEXTURE_W][TEXTURE_H][4], char *filename) {
	int i, j;
	FILE *fp;
	unsigned char bmp_buf[TEXTURE_W*TEXTURE_H*3];
	unsigned char header_buf[54];

	if( (fp = fopen( filename, "rb" )) == NULL ) {
		printf("Load texture failed: %s\n", filename);
		cleanup();
		exit(EXIT_FAILURE);
	}
	fread( header_buf, sizeof(char), 54, fp ); // ヘッダ部分(54バイト)
	fread( bmp_buf, sizeof(unsigned char), TEXTURE_W*TEXTURE_H*3, fp );
	fclose(fp);
	printf("Loaded texture: %s\n", filename);

	for( i = 0; i < TEXTURE_H; i++ ) {
		for( j = 0; j < TEXTURE_W; j++ ) {
			pixels[i][j][0] = (GLubyte)bmp_buf[i*TEXTURE_W*3 + j*3 + 2];
			pixels[i][j][1] = (GLubyte)bmp_buf[i*TEXTURE_W*3 + j*3 + 1];
			pixels[i][j][2] = (GLubyte)bmp_buf[i*TEXTURE_W*3 + j*3 + 0];
			pixels[i][j][3] = (GLubyte)255; // アルファ
		}
	}
}

// ■libcurlからのコールバック関数 - 受け取ったデータをファイルに書き込み
static size_t writeXmlToFile(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
	return fwrite(ptr, size, nmemb, stream);
}

