#include <windows.h>
#include <jni.h>
#include <assert.h>
#include "gnu_javax_sound_sampled_wce_WCECallbackWindowThread.h"
#include "gnu_javax_sound_sampled_wce_WCESourceDataLine.h"
#include "gnu_javax_sound_sampled_wce_WCETargetDataLine.h"
#include "wcesound.h"

#define AudioSystem_NOT_SPECIFIED -1L

/**
 * コールバックウインドウのウインドウクラス名
 */
#define	CALLBACK_WINDOW_CLASS_NAME	_T("WCE sound callback")

/**
 * 同時にオープンできるHWAVEINの最大数
 */
#define MAX_WAVEIN_COUNT	16

/**
 * 同時にオープンできるHWAVEOUTの最大数
 */
#define MAX_WAVEOUT_COUNT	16

/**
 * 出力バッファ数
 */
#define WAVEHDR_COUNT	2

// 入力バッファは読み込み可能
#define READABLE_FLAG	(1 << 31)

// 入力バッファの先頭を示すフラグ
#define READ_HEAD_FLAG	(1 << 30)

/**
 * バッファ内の残りデータサイズを取得するためのマスク
 * dwUser & READ_REMAINS_MASK とすることで、lpData内の残りデータ量を
 * 取得することができる。
 * 以下の式で lpData のデータ開始オフセットを取得できる。
 * hdr->dwBytesRecorded - (hdr->dwUser & READ_REMAINS_MASK)
 */
#define READ_REMAINS_MASK 0x1FFFFFFF

/**
 * 読み込みバッファの最大サイズ
 * READ_OFFSET_MASKに収まる値にしなければならない
 */
#define READ_MAX_BUFFER_SIZE (READ_REMAINS_MASK - 1)

// READ_REMAINS
#define SET_READ_REMAINS(dwUser, remains) \
{ \
	assert((remains) < READ_MAX_BUFFER_SIZE); \
	(dwUser) &= ~READ_REMAINS_MASK; \
	(dwUser) |= ((remains) & READ_REMAINS_MASK); \
}

// GET_REMAINS
#define GET_READ_REMAINS(dwUser) ((dwUser) & READ_REMAINS_MASK)

// 出力バッファは書き込み可能
#define WRITABLE_FLAG	1

/**
 * 音声入出力処理用構造体
 */
typedef struct
{
  struct
  {
    /**
     * この構造体は使用中
     */
    int in_use : 1;

    /**
     * リセット状態
     */
    int reset : 1;
  
    /**
     * 停止状態
     */
    int stopped : 1;


    /**
     * クローズ処理中
      */
    int closing : 1;
  };

  /**
   * クリティカルセクション
   * この構造体メンバの読み書き時に使用する。
   */
  CRITICAL_SECTION lock;

  /**
   * WAVEHDRが利用可能になった時点で
   * シグナル状態になるイベント
   */
  HANDLE hHeaderEvent;

  /**
   * 最大バッファサイズ
   */
  int max_buffer_size;

  /**
   * WAVEFORMATEX
   */
  WAVEFORMATEX	waveformat;

  union
  {
    /**
     * WAVE出力デバイスハンドル
     */ 
    HWAVEOUT hWaveOut;

    /**
     * WAVE入力デバイスハンドル
     */
    HWAVEIN hWaveIn;
  };
	
  /**
   * WAVEHDR構造体
   */
  WAVEHDR headers[WAVEHDR_COUNT];

  /**
   * WCESourceDataLine/WCETargetDataLineのインスタンス
   */
  jobject instance;

} wave_data;

typedef wave_data	wavein_data;
typedef wave_data	waveout_data;

/**
 * WAVE入力デバイス
 */
static wavein_data g_wavein_data_array[MAX_WAVEIN_COUNT];

/**
 * WAVE出力
 */
static waveout_data g_waveout_data_array[MAX_WAVEOUT_COUNT];

// コールバックウインドウハンドル
HWND g_hwndCallback;

// グローバルロック
static CRITICAL_SECTION g_lock;

// WCEEventQueue
static jobject g_eventQueue;

// WCEEventQueue.postEvent()
static jmethodID g_postEvent_id;

// LineEvent.Type
static jobject g_LineEvent_Type_OPEN;
static jobject g_LineEvent_Type_CLOSE;
static jobject g_LineEvent_Type_START;
static jobject g_LineEvent_Type_STOP;

LRESULT CALLBACK CallbackWndProc(HWND, UINT, WPARAM, LPARAM); 

static BOOL prepare_references(JNIEnv* env, jobject instance, jobject eventQueue);
static void unprepare_references(JNIEnv* env, jobject instance, jobject eventQueue);
static BOOL create_callback_window(JNIEnv* env);
static wavein_data* get_wavein_data(HWAVEIN hWaveIn);
static waveout_data* get_waveout_data(HWAVEOUT hWaveOut);
static void close_wavein_data(waveout_data* data);
static void close_waveout_data(waveout_data* data);
static void post_LineEvent(JNIEnv* env, jobject line, jobject eventType, jlong position);
static BOOL is_wavein_active(wavein_data* data);
static BOOL is_waveout_active(waveout_data* data);
static int lock_readable_header(wavein_data* data);
static void unlock_readable_header(wavein_data* data, int index, DWORD remains);
static int lock_writable_header(waveout_data* data);
static int get_writable_header_count(waveout_data* data);

static void init_waveformat(LPWAVEFORMATEX pwfe,
							int sampleRate,
							int sampleSizeInBits,
							int channels,
							int frameSize,
							int frameRate);
static int adjust_max_buffer_size(int bufferSize, WAVEFORMATEX* wfe);
static BOOL alloc_header_data(int buffer_size, WAVEHDR* headers);
static void free_header_data(WAVEHDR* headers);

/**
 * DLLMain
 */
BOOL APIENTRY DllMain(HANDLE hinstDLL,  // DLL モジュールのハンドル
                    DWORD fdwReason,     // 関数を呼び出す理由
                    LPVOID lpvReserved   // 予約済み
					)
{
  switch (fdwReason)
  {
  case DLL_PROCESS_ATTACH:
    {
      // スレッドのアタッチ／デタッチには興味がないことを伝える
      DisableThreadLibraryCalls((HMODULE) hinstDLL);
      // クリティカルセクションを初期化する
      InitializeCriticalSection(&g_lock);

    }
    return TRUE;

  case DLL_PROCESS_DETACH:
    {
      int i;
      // クリティカルセクションを削除する
      DeleteCriticalSection(&g_lock);
      // 使用中のデバイスハンドルをすべてクローズする(不要？）
      for (i = 0; i < MAX_WAVEIN_COUNT; ++i)
      {
        wavein_data* data = &g_wavein_data_array[i];
	if (data->in_use)
        {
	  int j;
	  waveInReset(data->hWaveIn);
	  for (j = 0; j < WAVEHDR_COUNT; ++j)
          {
	    waveInUnprepareHeader(data->hWaveIn, &data->headers[j], sizeof(WAVEHDR));
	  }
	  waveInClose(data->hWaveIn);
	}
      }
      for (i = 0; i < MAX_WAVEOUT_COUNT; ++i)
      {
        waveout_data* data = &g_waveout_data_array[i];
	if (data->in_use)
        {
          int j;
          waveOutReset(data->hWaveOut);
          for (j = 0; j < WAVEHDR_COUNT; ++j)
          {
            waveOutUnprepareHeader(data->hWaveOut, &data->headers[j], sizeof(WAVEHDR));
          }
          waveOutClose(data->hWaveOut);
        }
      }
    }
    return TRUE;
  }
  return FALSE;
}

/**
 * Javaオブジェクト等の参照を初期化する
 */
static BOOL prepare_references(JNIEnv* env, jobject instance, jobject eventQueue)
{
  jfieldID open_id, close_id, start_id, stop_id;

  jclass clazz = env->FindClass("javax/sound/sampled/LineEvent$Type");
  if (! clazz)
  {
    throw_RuntimeException(env, "Failed to get LineEvent.Type");
    return FALSE;
  }
  // メソッドIDを取得しておく
  g_postEvent_id = env->GetMethodID(env->GetObjectClass(eventQueue),
    				 "postEvent",
				 "(Ljavax/sound/sampled/Line;Ljavax/sound/sampled/LineEvent$Type;J)V");
  if (! g_postEvent_id)
  {
    throw_RuntimeException(env, "Failed to get methodID");
    return FALSE;
  }
	
  // LineEvent.Typeのstatic変数を保持しておく
  open_id = env->GetStaticFieldID(clazz, "OPEN", "Ljavax/sound/sampled/LineEvent$Type;");
  close_id = env->GetStaticFieldID(clazz, "CLOSE", "Ljavax/sound/sampled/LineEvent$Type;");
  start_id = env->GetStaticFieldID(clazz, "START", "Ljavax/sound/sampled/LineEvent$Type;");
  stop_id = env->GetStaticFieldID(clazz, "STOP", "Ljavax/sound/sampled/LineEvent$Type;");
  g_LineEvent_Type_OPEN = env->GetStaticObjectField(clazz, open_id);
  g_LineEvent_Type_CLOSE = env->GetStaticObjectField(clazz, close_id);
  g_LineEvent_Type_START = env->GetStaticObjectField(clazz, start_id);
  g_LineEvent_Type_STOP = env->GetStaticObjectField(clazz, stop_id);
	
  if (env->ExceptionOccurred())
  {
    return FALSE;
  }

  // グローバル参照として登録しておく
  g_eventQueue = env->NewGlobalRef(eventQueue);	
  g_LineEvent_Type_OPEN = env->NewGlobalRef(g_LineEvent_Type_OPEN);
  g_LineEvent_Type_CLOSE = env->NewGlobalRef(g_LineEvent_Type_CLOSE);
  g_LineEvent_Type_START = env->NewGlobalRef(g_LineEvent_Type_START);
  g_LineEvent_Type_STOP = env->NewGlobalRef(g_LineEvent_Type_STOP);

  return TRUE;
}

/**
 * Javaオブジェクト等の参照を初期化する
 */
static void unprepare_references(JNIEnv* env, jobject instance, jobject eventQueue)
{
  // グローバル参照を解除する
  env->DeleteGlobalRef(g_eventQueue);
  env->DeleteGlobalRef(g_LineEvent_Type_OPEN);
  env->DeleteGlobalRef(g_LineEvent_Type_CLOSE);
  env->DeleteGlobalRef(g_LineEvent_Type_START);
  env->DeleteGlobalRef(g_LineEvent_Type_STOP);
}

/**
 * コールバックウインドウを作成する
 */
static BOOL create_callback_window(JNIEnv* env)
{
  HINSTANCE hInst = GetModuleHandle(NULL);
  WNDCLASS wndclass = {0};

  // コールバックウインドウを作成する
  wndclass.lpfnWndProc = CallbackWndProc;
  wndclass.hInstance = hInst;
  wndclass.lpszClassName = CALLBACK_WINDOW_CLASS_NAME;

  if (! RegisterClass(&wndclass))
  {
    throw_RuntimeException(env, "RegisterClass() failed");
    return FALSE;
  }

  g_hwndCallback = CreateWindow(CALLBACK_WINDOW_CLASS_NAME,
				NULL,
				WS_DISABLED,
				0,
				0,
				0,
				0,
				NULL,
				NULL,
				hInst,
				NULL);
  if (! g_hwndCallback)
  {
    throw_RuntimeException(env, "CreateWindow() failed");
    return FALSE;
  }

  // JNIEnv を登録しておく
  SetWindowLong(g_hwndCallback, GWL_USERDATA, (LONG) env);

  return TRUE;
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCECallbackWindowThread
 * Method:    messageLoop
 * Signature: (Lgnu/javax/sound/sampled/wce/WCEEventQueue;)V
 */
JNIEXPORT void JNICALL Java_gnu_javax_sound_sampled_wce_WCECallbackWindowThread_messageLoop
(JNIEnv *env, jobject instance, jobject eventQueue)
{
  // オブジェクト関連の初期化を行う
  if (! prepare_references(env, instance, eventQueue))
  {
    return;
  }
	
  // コールバックウインドウを作成する
  if (! create_callback_window(env))
  {
    return;
  }


  // 初期化が終わったことを通知する
  {
    jmethodID mid = env->GetMethodID(env->GetObjectClass(instance),
      				"nativeWindowInitialized",
				"()V");
    if (! mid)
    {
      throw_RuntimeException(env, "Failed to get methodID");
      return;
    }
    env->CallVoidMethod(instance, mid);
  }

  // メッセージループを開始する
  {
    MSG msg;
    while (GetMessage(&msg, g_hwndCallback, 0, 0) > 0)
    {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }

  // 参照を解除する
  unprepare_references(env, instance, eventQueue);
}

/**
 * コールバックウインドウプロシージャ
 */
LRESULT CALLBACK CallbackWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch (uMsg)
  {
  case MM_WIM_OPEN:
    // 入力デバイスがオープンされた
    {
      // JNIEnvを取得しておく
      JNIEnv* env = (JNIEnv*) GetWindowLong(hwnd, GWL_USERDATA);
      HWAVEIN hWaveIn = (HWAVEIN) wParam;
      wavein_data* data = get_wavein_data(hWaveIn);
      if (data)
      {
	// メッセージを通知する
	post_LineEvent(env,
        	 data->instance,
		 g_LineEvent_Type_OPEN,
		 AudioSystem_NOT_SPECIFIED);
      }
    }
    break;

  case MM_WIM_CLOSE:
    // 入力デバイスがクローズされた
    {
      // JNIEnvを取得しておく
      JNIEnv* env = (JNIEnv*) GetWindowLong(hwnd, GWL_USERDATA);
      HWAVEIN hWaveIn = (HWAVEIN) wParam;
      waveout_data* data = get_wavein_data(hWaveIn);
      if (data)
      {
        // CLOSEメッセージを通知する
	post_LineEvent(env,
          	   data->instance,
		   g_LineEvent_Type_CLOSE,
		   AudioSystem_NOT_SPECIFIED);
	// wavein_dataを閉じる
	close_wavein_data(data);
      }
    }
    break;

  case MM_WIM_DATA:
    // 入力バッファが満杯になった
    {
      // JNIEnvを取得しておく
      JNIEnv* env = (JNIEnv*) GetWindowLong(hwnd, GWL_USERDATA);
      HWAVEIN hWaveIn = (HWAVEIN) wParam;
      wavein_data* data = get_wavein_data(hWaveIn);
      if (data)
      {
        int i;
        // クリティカルセクションに入る
        EnterCriticalSection(&data->lock);
        if (data->reset)
        {
          // リセットされた場合
          data->reset = FALSE;
          for (i = 0; i < WAVEHDR_COUNT; ++i)
          {
            WAVEHDR* hdr = (WAVEHDR*) lParam;
            if (data->closing)
            {
              // クローズ処理中
              // 読み取り可能フラグを立てる（読み出し可能バイト数は０）
              SET_READ_REMAINS(hdr->dwUser, 0);
              hdr->dwUser |= READABLE_FLAG;
            }
            else
            {
              MMRESULT mmresult;
              // 読み込み可能フラグをクリアする
              hdr->dwUser &= ~READABLE_FLAG;
              // 再度キューに入れる
              mmresult = waveInAddBuffer(data->hWaveIn,
                hdr,
                sizeof(WAVEHDR));
              if (mmresult != MMSYSERR_NOERROR)
              {
                throw_MMRESULT_Exception(env, mmresult);
              }
            }
          }
        }
        else
        {
          // バッファが満杯になった場合、以下の処理を行う
          // o バッファの「残りバイト数」をdwBytesRecordedに設定する
          // o 「読み取り可能」フラグを立てる
          WAVEHDR* hdr = (WAVEHDR*) lParam;
          SET_READ_REMAINS(hdr->dwUser, hdr->dwBytesRecorded);
          // 読み取り可能フラグを立てる
          hdr->dwUser |= READABLE_FLAG;
        }
        // イベントを通知する
        if (! SetEvent(data->hHeaderEvent))
        {
          throw_RuntimeException(env, "SetEvent() failed");
        }
        // アクティブかどうかを調べる
        if (! is_wavein_active(data))
        {
          // アクティブ状態でなくなった場合は、
          // STOPイベントを通知する
          post_LineEvent(env,
            data->instance,
            g_LineEvent_Type_STOP,
            AudioSystem_NOT_SPECIFIED);
        }
        // クリティカルセクションから抜ける
        LeaveCriticalSection(&data->lock);
      }
    }
    break;
    
  case MM_WOM_OPEN:
    {
      // JNIEnvを取得しておく
      JNIEnv* env = (JNIEnv*) GetWindowLong(hwnd, GWL_USERDATA);
      HWAVEOUT hWaveOut = (HWAVEOUT) wParam;
      waveout_data* data = get_waveout_data(hWaveOut);
      if (data) {
        // メッセージを通知する
        post_LineEvent(env,
          data->instance,
          g_LineEvent_Type_OPEN,
          AudioSystem_NOT_SPECIFIED);
      }
    }
    break;
  case MM_WOM_CLOSE:
    {
      // JNIEnvを取得しておく
      JNIEnv* env = (JNIEnv*) GetWindowLong(hwnd, GWL_USERDATA);
      HWAVEOUT hWaveOut = (HWAVEOUT) wParam;
      waveout_data* data = get_waveout_data(hWaveOut);
      if (data) {
        // メッセージを通知する
        post_LineEvent(env,
          data->instance,
          g_LineEvent_Type_CLOSE,
          AudioSystem_NOT_SPECIFIED);
        // waveout_dataを閉じる
        close_waveout_data(data);
      }
    }
    break;
  case MM_WOM_DONE:
    {
      // JNIEnvを取得しておく
      JNIEnv* env = (JNIEnv*) GetWindowLong(hwnd, GWL_USERDATA);
      HWAVEOUT hWaveOut = (HWAVEOUT) wParam;
      waveout_data* data = get_waveout_data(hWaveOut);
      if (data) {
        int i;
        WAVEHDR* whdr = (WAVEHDR*) lParam;
        // クリティカルセクションに入る
        EnterCriticalSection(&data->lock);
        if (data->reset) {
          // リセットされた場合
          data->reset = FALSE;
          // すべてのヘッダの状態を更新する
          for (i = 0; i < WAVEHDR_COUNT; ++i) {
            if (! (whdr->dwUser & WRITABLE_FLAG)) {
              // 書き込み可能フラグを立てる
              whdr->dwUser |= WRITABLE_FLAG;
            }
          }
        } else {
          // バッファの再生が正常に完了した場合
          // 書き込み可能ビットを立てる
          whdr->dwUser |= WRITABLE_FLAG;
        }
	// イベントを通知する
        if (! SetEvent(data->hHeaderEvent)) {
          throw_RuntimeException(env, "SetEvent() failed");
        }
        // アクティブかどうかを調べる
        if (! is_waveout_active(data)) {
          // アクティブ状態でなくなった場合は、
          // STOPイベントを通知する
          post_LineEvent(env,
            data->instance,
            g_LineEvent_Type_STOP,
            AudioSystem_NOT_SPECIFIED);
        }
        // クリティカルセクションから抜ける
        LeaveCriticalSection(&data->lock);
      }
    }
    break;
  default:
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
  }
  return 0;
}

/**
 * 指定されたHWAVEINに対応する wavein_data構造体へのポインタを返す。
 * NULLを指定された場合、新しいwavein_data構造体へのポインタを返す。
 */
static wavein_data* get_wavein_data(HWAVEIN hWaveIn)
{
  int i;
  wavein_data* result = NULL;
  EnterCriticalSection(&g_lock);
  if (! hWaveIn)
  {
    for (i = 0; i < MAX_WAVEIN_COUNT; ++i)
    {
      if (! g_wavein_data_array[i].in_use)
      {
        // 空きスロット
	result = &g_wavein_data_array[i];
        // 使用中かつ停止状態に設定する
	result->in_use = TRUE;
	result->stopped = TRUE;
	break;
      }
    }
  }
  else
  {
    for (i = 0; i < MAX_WAVEIN_COUNT; ++i)
    {
      if (g_wavein_data_array[i].hWaveIn == hWaveIn
          && g_wavein_data_array[i].in_use)
      {
        result = &g_wavein_data_array[i];
        break;
      }
    }
  }
  LeaveCriticalSection(&g_lock);

  return result;
}

/**
 * 指定されたHWAVEOUTに対応する waveout_data構造体へのポインタを返す。
 * NULLを指定された場合、新しいwaveout_data構造体へのポインタを返す。
 */
static waveout_data* get_waveout_data(HWAVEOUT hWaveOut) {
	int i;
	waveout_data* result = NULL;
	EnterCriticalSection(&g_lock);
	if (! hWaveOut) {
		for (i = 0; i < MAX_WAVEOUT_COUNT; ++i) {
			if (! g_waveout_data_array[i].in_use) {
				// 空きスロット
				result = &g_waveout_data_array[i];
				// 使用中かつ停止状態に設定する
				result->in_use = TRUE;
				result->stopped = TRUE;
				break;
			}
		}
	} else {
		for (i = 0; i < MAX_WAVEOUT_COUNT; ++i) {
			if (g_waveout_data_array[i].hWaveOut == hWaveOut
					&& g_waveout_data_array[i].in_use) {
				result = &g_waveout_data_array[i];
				break;
			}
		}
	}
	LeaveCriticalSection(&g_lock);

	return result;
}

/**
 * 指定されたwavein_data構造体をクローズする。
 */
static void close_wavein_data(wavein_data* data) {
	int i;
	if (! data) {
		return;
	}
	EnterCriticalSection(&g_lock);
	for (i = 0; i < MAX_WAVEIN_COUNT; ++i) {
		if (&g_wavein_data_array[i] == data) {
			// 割り当てられたリソースをすべて開放する
			DeleteCriticalSection(&data->lock);
			CloseHandle(data->hHeaderEvent);
			free_header_data(data->headers);

			// 内容を0クリアしておく
			memset(data, 0, sizeof(wavein_data));
			break;
		}
	}
	LeaveCriticalSection(&g_lock);
}

/**
 * 指定されたwaveout_data構造体をクローズする。
 */
static void close_waveout_data(waveout_data* data) {
	int i;

	if (! data) {
		return;
	}

	EnterCriticalSection(&g_lock);
	for (i = 0; i < MAX_WAVEOUT_COUNT; ++i) {
		if (&g_waveout_data_array[i] == data) {
			// 割り当てられたリソースをすべて開放する
			DeleteCriticalSection(&data->lock);
			CloseHandle(data->hHeaderEvent);
			free_header_data(data->headers);

			// 内容を0クリアしておく
			memset(data, 0, sizeof(waveout_data));
			break;
		}
	}
	LeaveCriticalSection(&g_lock);
}

/**
 * イベントをpostする
 */
static void post_LineEvent(JNIEnv* env, jobject line, jobject eventType, jlong position) {
	env->CallVoidMethod(
          g_eventQueue,
          g_postEvent_id,
          line,
          eventType,
          position);
}

/**
 * TargetDataLineがアクティブかを調べる
 */
static BOOL is_wavein_active(wavein_data* data) {
	int i;
	BOOL active = FALSE;
	EnterCriticalSection(&data->lock);
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		if ((! data->stopped) && (! (data->headers[i].dwUser & READABLE_FLAG))) {
			// 読み込み不可能なバッファがあれば
			// アクティブ状態にあると判断する
			active = TRUE;
			break;
		}
	}
	LeaveCriticalSection(&data->lock);
	return active;
}

/**
 * SourceDataLineがアクティブかを調べる
 */
static BOOL is_waveout_active(waveout_data* data) {
	int i;
	BOOL active = FALSE;
	EnterCriticalSection(&data->lock);
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		if (! (data->headers[i].dwUser & WRITABLE_FLAG)) {
			// 書き込み不可能なバッファがあれば
			// アクティブ状態にあると判断する
			active = TRUE;
			break;
		}
	}
	LeaveCriticalSection(&data->lock);
	return active;
}

/**
 * 読み出し用にバッファをロックし、そのインデックス値を返す。
 * ロックできない場合には負数を返す。
 */
static int lock_readable_header(wavein_data* data) {
	int result = -1;
	int i;
	EnterCriticalSection(&data->lock);
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		// 以下の条件を満たすWAVEHDRを探す
		// o 読み出し可能である
		// o 次に読み出すべきバッファである
		DWORD dwUser = data->headers[i].dwUser;
		if ((dwUser & READABLE_FLAG) && (dwUser & READ_HEAD_FLAG)) {
			// READABLE_FLAGをクリアする
			dwUser &= ~READABLE_FLAG;
			data->headers[i].dwUser = dwUser;
			result = i;
			break;
		}
	}
	LeaveCriticalSection(&data->lock);
	return result;
}

/**
 * 指定されたインデックス位置のバッファをアンロックする。
 * その際に「残りバイト数」を指定された値に更新する。
 * 残りバイト数が0になった場合、READ_HEAD_FLAGはクリアされ、次のWAVEHDRの
 * READ_HEAD_FLAGが立てられる。
 */
static void unlock_readable_header(wavein_data* data,
			     int index,
			     DWORD remains) {
	WAVEHDR* hdr = &data->headers[index];

	// クリティカルセクションに入る
	EnterCriticalSection(&data->lock);

	// デバッグ用
	assert(! (hdr->dwUser & READABLE_FLAG));
	assert(hdr->dwUser & READ_HEAD_FLAG);
	assert(remains <= READ_MAX_BUFFER_SIZE);

	// remainsを下位ビットに入れる
	SET_READ_REMAINS(hdr->dwUser, remains);
	if (! remains) {
		// バッファの残りがなくなった場合、READ_HEAD_FLAGをクリアして、
		// 次のWAVEHDRのREAD_HEAD_FLAGを立てる
		int next_index = (index + 1) >= WAVEHDR_COUNT
				? 0
				: index + 1;
		data->headers[next_index].dwUser |= READ_HEAD_FLAG;
		hdr->dwUser &= ~READ_HEAD_FLAG;

	} else {
		// まだバッファに残りがある場合は、
		// READABLE_FLAGを再度立てる
		hdr->dwUser |= READABLE_FLAG;
	}
	
	// クリティカルセクションから抜ける
	LeaveCriticalSection(&data->lock);
}


/**
 * 書き込み可能バッファをロックし、インデックスを返す。
 */
static int lock_writable_header(waveout_data* data) {
	int i, result = -1;
	EnterCriticalSection(&data->lock);
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		if (data->headers[i].dwUser & WRITABLE_FLAG) {
			data->headers[i].dwUser &= ~WRITABLE_FLAG;
			result = i;
			break;
		}
	}
	LeaveCriticalSection(&data->lock);
	return result;
}

/**
 * 書き込み可能バッファ数を返す
 */
static int get_writable_header_count(waveout_data* data) {
	int i, result = 0;
	EnterCriticalSection(&data->lock);
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		if (data->headers[i].dwUser & WRITABLE_FLAG) {
			result++;
		}
	}
	LeaveCriticalSection(&data->lock);
	return result;
}

/**
 * WAVEFORMATEX構造体を初期化する
 */
static void init_waveformat(LPWAVEFORMATEX pwfe,
							int sampleRate,
							int sampleSizeInBits,
							int channels,
							int frameSize,
							int frameRate) {
	pwfe->wFormatTag = WAVE_FORMAT_PCM;
	pwfe->nChannels = (WORD) channels;
	pwfe->wBitsPerSample = (WORD) sampleSizeInBits;
	pwfe->nSamplesPerSec = (DWORD) sampleRate;
	pwfe->nBlockAlign = (WORD) frameSize;
	pwfe->nAvgBytesPerSec = (DWORD) (frameSize * frameRate);
}

/**
 * 最大バッファサイズを調整し、その値を返す。
 */
static int adjust_max_buffer_size(int bufferSize, WAVEFORMATEX* wfe) {
	int max_buffer_size = 0;

	// バッファサイズを決定する
	if (bufferSize && (bufferSize % wfe->nBlockAlign) == 0) {
		// バッファサイズがブロックアラインの倍数になっている場合
		// そのバッファサイズを使用する
		max_buffer_size = bufferSize;
	} else {
		// 指定されたブロックサイズをブロックアラインにあわせる
		int n = bufferSize / wfe->nBlockAlign;
		if (! n) {
			n = wfe->nBlockAlign;
		}
		max_buffer_size = wfe->nSamplesPerSec * n;
	}
	return max_buffer_size;
}

/**
 * WAVEHDR.lpDataにメモリを割り当てる
 */
static BOOL alloc_header_data(int max_buffer_size, WAVEHDR* headers) {
	int i;
	BOOL result = TRUE;
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		LPSTR buff = (LPSTR) malloc(max_buffer_size);
		if (! buff) {
			// メモリ不足
			int j;
			for (j = i - 1; j >= 0; --j) {
				// それまでに割り当てたメモリを開放する
				free(headers[j].lpData);
			}
			result = FALSE;
			break;
		}
		headers[i].lpData = buff;
		headers[i].dwBufferLength = max_buffer_size;
	}
	return result;
}

/**
 * WAVEHDR.lpDataに割り当てられたメモリを開放する
 */
static void free_header_data(WAVEHDR* headers) {
	int i;
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		free(headers[i].lpData);
	}
}

/**
 * RuntimeExceptionをthrowする
 */
void throw_RuntimeException(JNIEnv* env, const char* msg) {
	jclass clazz = env->FindClass("java/lang/RuntimeException");
	if (clazz) {
		env->ThrowNew(clazz, msg);
	}
}

void throw_LineUnavailableException(JNIEnv* env, const char* msg) {
	jclass clazz = env->FindClass("javax/sound/sampled/LineUnavailableException");
	if (clazz) {
		env->ThrowNew(clazz, msg);
	}
}

void throw_MMRESULT_Exception(JNIEnv* env, MMRESULT mmresult) {
	jclass clazz;
	jstring msg;
	_TCHAR buff[256];
	
	// メッセージを取得する
	waveOutGetErrorText(mmresult, buff, sizeof(buff) / sizeof(buff[0]));

	// メッセージを指定して、例外をthrowする。
	msg = env->NewString((const jchar*) buff, _tcslen(buff));
	if (msg) {
		clazz = env->FindClass("javax/sound/sampled/LineUnavailableException");
		if (clazz) {
			jmethodID mid = env->GetMethodID(clazz, "<init>", "(Ljava/lang/String;)V");
			jthrowable t = (jthrowable) env->NewObject(clazz, mid, msg);
			env->Throw(t);
		}
	}
}

//----------------------------------------------------
//--- これ以降は SourceDataLine ----------------------
//----------------------------------------------------

/*
 * Class:     gnu_javax_sound_sampled_wce_WCESourceDataLine
 * Method:    openNative
 * Signature: (ILjavax/sound/sampled/AudioFormat$Encoding;FIIIFZ)I
 */
JNIEXPORT jint JNICALL Java_gnu_javax_sound_sampled_wce_WCESourceDataLine_openNative
						(JNIEnv *env,
						 jobject instance,
						 jint bufferSize,
						 jfloat sampleRate,
						 jint sampleSizeInBits,
						 jint channels,
						 jint frameSize,
						 jfloat frameRate,
						 jboolean bigEndian) {
	int i;
	MMRESULT mmresult;
	
	// waveout_dataを確保する
	waveout_data* data = get_waveout_data(NULL);
	if (! data) {
		throw_LineUnavailableException(env, "Too many waveout_data");
		return 0;
	}
	
	// 確保した構造体の内容を初期化する
	InitializeCriticalSection(&data->lock);
	data->hHeaderEvent = CreateEvent(NULL,
									     FALSE,
										 FALSE,
										 NULL);
	data->instance = instance;

	// フォーマットの内容から、WAVEFORMATEX構造体を初期化する
	init_waveformat(&data->waveformat,
					(int) sampleRate,
					sampleSizeInBits,
					channels,
					frameSize,
					(int) frameRate);

	// バッファサイズを決定する
	data->max_buffer_size = adjust_max_buffer_size(bufferSize, &data->waveformat);

	// バッファを確保する
	// バッファを確保する
	if (! alloc_header_data(data->max_buffer_size, data->headers)) {
		close_waveout_data(data);
		throw_LineUnavailableException(env, "Not enough memory");
		return 0;
	}

	// 出力デバイスをオープンする
	mmresult = waveOutOpen(&data->hWaveOut,
						   WAVE_MAPPER,
						   &data->waveformat,
						   (DWORD) g_hwndCallback,
						   0,
						   CALLBACK_WINDOW);
	if (mmresult != MMSYSERR_NOERROR) {
		// エラー処理
		close_waveout_data(data);
		throw_MMRESULT_Exception(env, mmresult);
		return 0;
	}

	{
		// ボリューム取得テスト
		DWORD volume;
		waveOutGetVolume(data->hWaveOut, &volume);
		volume = volume;
	}

	// WAVEHDRを準備状態にする
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		data->headers[i].dwLoops = 1;

		mmresult = waveOutPrepareHeader(data->hWaveOut,
						&data->headers[i],
						sizeof(WAVEHDR));
		if (mmresult != MMSYSERR_NOERROR) {
			waveOutClose(data->hWaveOut);
			close_waveout_data(data);
			throw_MMRESULT_Exception(env, mmresult);
			return 0;
		}
		// 書き込み可能フラグを立てておく
		data->headers[i].dwUser |= WRITABLE_FLAG;
	}

	return (jint) data;
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCESourceDataLine
 * Method:    writeNative
 * Signature: (I[BII)I
 */
JNIEXPORT jint JNICALL Java_gnu_javax_sound_sampled_wce_WCESourceDataLine_writeNative
(JNIEnv *env, jobject instance, jint nativeData, jbyteArray b, jint off, jint len) {
	char* buff;
	int bytes_left;
	char* p;

	// waveout_data構造体のポインタを得る
	waveout_data* data = (waveout_data*) nativeData;
	if (! data) {
		return 0;
	}

	// 指定されたバイト長を、バッファのアラインに合わせる
	len -= (len % data->waveformat.nBlockAlign);
	if (len <= 0) {
		return 0;
	}

	// バッファのポインタを得る
	buff = (char*) env->GetByteArrayElements(b, NULL);
	bytes_left = len;
	p = buff + off;

	// すべてのバイトを書き込むまで以下の処理を繰り返す
	while (bytes_left > 0) {
		// アクティブ状態を調べておく
		BOOL active = is_waveout_active(data);
		WAVEHDR* hdr = NULL;
		int write_count, writable_header_index;

		// 書き込み可能ヘッダを取得する
		while ((writable_header_index = lock_writable_header(data)) < 0) {
			// イベント通知を待つ
			if (WaitForSingleObject(data->hHeaderEvent, INFINITE) != WAIT_OBJECT_0) {
				// エラー発生
				// throw_RuntimeException(env,  "WaitForSingleObject() failed");
				goto END;
			}
			// この時点で/stop()のいずれかが呼び出されていたら
			// 即座にリターンする
			// ToDo: flush()でもリターンするようにする
			if (data->stopped) {
				break;
			}
		}
		if (data->stopped) {
			goto END;
		}

		if (writable_header_index < 0) {
			// 何らかの原因でロックできなかった場合はリターンする
			goto END;
		}

		// クリティカルセクションに入る
		EnterCriticalSection(&data->lock);

		// 書き込むバッファを決定する
		hdr = &data->headers[writable_header_index];
		// 書き込むべきバイト数を計算し、バッファにコピーする
		write_count = (bytes_left <= data->max_buffer_size)
						? bytes_left
						: data->max_buffer_size;
		memcpy(hdr->lpData, p, write_count);
		hdr->dwBufferLength = write_count;

		// クリティカルセクションから抜ける
		LeaveCriticalSection(&data->lock);

		// 再生キューに入れる
		if (waveOutWrite(data->hWaveOut, hdr, sizeof(WAVEHDR))
				== MMSYSERR_NOERROR) {
			if (! active) {
				// アクティブ状態が変化する場合は
				// STARTイベントを送信する
				post_LineEvent(env,
						 data->instance,
						 g_LineEvent_Type_START,
						 AudioSystem_NOT_SPECIFIED);
			}
		} else {
			throw_RuntimeException(env, "waveOutWrite() failed");
			break;
		}

		// カウンタとポインタを更新する
		p += write_count;
		bytes_left -= write_count;

	}

END:
	env->ReleaseByteArrayElements(b, (jbyte*) buff, 0);
	// キューに入れたバイト数を返す
	return len - bytes_left;
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCESourceDataLine
 * Method:    nativeClose
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_gnu_javax_sound_sampled_wce_WCESourceDataLine_nativeClose
(JNIEnv *env, jobject instance, jint nativeData)
{
  waveout_data* data = (waveout_data*) nativeData;
  int i;
  MMRESULT mmresult;

  if (! data)
  {
    return;
  }

  // この関数が呼び出されて時点で、すでにwaveOutReset()が呼び出されている
  // ヘッダをクリーンアップする
  for (i = 0; i < WAVEHDR_COUNT; ++i)
  {
    mmresult = waveOutUnprepareHeader(data->hWaveOut, &data->headers[i], sizeof(WAVEHDR));
    if (mmresult != MMSYSERR_NOERROR)
    {
      throw_MMRESULT_Exception(env, mmresult);
      break;
    }
  }

  mmresult = waveOutClose(data->hWaveOut);
  if (mmresult != MMSYSERR_NOERROR)
  {
    throw_MMRESULT_Exception(env, mmresult);
  }
}


/*
 * Class:     gnu_javax_sound_sampled_wce_WCESourceDataLine
 * Method:    drainNative
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_gnu_javax_sound_sampled_wce_WCESourceDataLine_drainNative
(JNIEnv *env, jobject instance, jint nativeData)
{
  // waveout_data構造体のポインタを得る
  waveout_data* data = (waveout_data*) nativeData;
  if (! data)
  {
    return;
  }

  // すべてのヘッダが書き込み可能になるまで待つ
  while (get_writable_header_count(data) < WAVEHDR_COUNT)
  {
    if (WaitForSingleObject(data->hHeaderEvent, INFINITE) != WAIT_OBJECT_0)
    {
      // 例外をthrow
      env->ThrowNew(env->FindClass("java/lang/RuntimeException"), "WaitForSingleObject() failed");
      break;
    }
  }
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCESourceDataLine
 * Method:    flushNative
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_gnu_javax_sound_sampled_wce_WCESourceDataLine_flushNative
(JNIEnv *env, jobject instance, jint nativeData)
{
  // waveout_data構造体のポインタを得る
  waveout_data* data = (waveout_data*) nativeData;
  if (! data)
  {
    return;
  }

  // リセットフラグを設定しておく
  EnterCriticalSection(&data->lock);
  data->reset = TRUE;
  LeaveCriticalSection(&data->lock);

  // リセットする
  if (waveOutReset(data->hWaveOut) != MMSYSERR_NOERROR)
  {
    env->ThrowNew(env->FindClass("java/lang/RuntimeError"), "waveOutReset() failed");
    return;
  }
}


/*
 * Class:     gnu_javax_sound_sampled_wce_WCESourceDataLine
 * Method:    isNativeActive
 * Signature: (I)Z
 */
JNIEXPORT jboolean JNICALL Java_gnu_javax_sound_sampled_wce_WCESourceDataLine_isNativeActive
(JNIEnv *env, jobject instance, jint nativeData) {
	if (! nativeData) {
		return JNI_FALSE;
	}

	return is_waveout_active((waveout_data*) nativeData) ? JNI_TRUE : JNI_FALSE;
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCESourceDataLine
 * Method:    getNativeBufferSize
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_gnu_javax_sound_sampled_wce_WCESourceDataLine_getNativeBufferSize
(JNIEnv *env, jobject instance, jint nativeData) {
	// バッファサイズを返す
	if (! nativeData) {
		return AudioSystem_NOT_SPECIFIED;
	}

	return (jint) (((waveout_data*) nativeData)->max_buffer_size);
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCESourceDataLine
 * Method:    availableNative
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_gnu_javax_sound_sampled_wce_WCESourceDataLine_availableNative
(JNIEnv *env, jobject instance, jint nativeData) {
	int i;
	waveout_data* data = (waveout_data*) nativeData;
	jint result = 0;
	if (! data) {
		return 0;
	}

	EnterCriticalSection(&data->lock);
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		// バッファが１つあいている場合、そのサイズを返す
		if (data->headers[i].dwUser & WRITABLE_FLAG) {
			result = data->max_buffer_size;
			break;
		}
	}
	LeaveCriticalSection(&data->lock);
	return result;
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCESourceDataLine
 * Method:    startNative
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_gnu_javax_sound_sampled_wce_WCESourceDataLine_startNative
(JNIEnv *env, jobject instance, jint nativeData)
{
  // waveout_data構造体のポインタを得る
  waveout_data* data = (waveout_data*) nativeData;
  if (! data) {
    return;
  }

  if (waveOutRestart(data->hWaveOut) != MMSYSERR_NOERROR)
  {
    env->ThrowNew(env->FindClass("java/lang/RuntimeException"), "waveOutRestart() failed");
    return;
  }

  EnterCriticalSection(&data->lock);
  data->stopped = FALSE;
  LeaveCriticalSection(&data->lock);

  // 再生中のバッファが存在する場合、STARTイベントを送信する
  if (is_waveout_active(data))
  {
    post_LineEvent(env,
      data->instance,
      g_LineEvent_Type_START,
      AudioSystem_NOT_SPECIFIED);
  }
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCESourceDataLine
 * Method:    stopNative
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_gnu_javax_sound_sampled_wce_WCESourceDataLine_stopNative
(JNIEnv *env, jobject instance, jint nativeData) {
	// waveout_data構造体のポインタを得る
	waveout_data* data = (waveout_data*) nativeData;
	if (! data) {
		return;
	}

	// stoppedフラグを設定する
	EnterCriticalSection(&data->lock);
	data->stopped = TRUE;
	LeaveCriticalSection(&data->lock);

	if (waveOutPause(data->hWaveOut) != MMSYSERR_NOERROR) {
		env->ThrowNew(env->FindClass("java/lang/RuntimeException"), "waveOutPause() failed");
		return;
	}

	// STOPイベントを送信する
	post_LineEvent(env,
			 data->instance,
			 g_LineEvent_Type_STOP,
			 AudioSystem_NOT_SPECIFIED);
	return;
}

//----------------------------------------------------
//--- ここまでが SourceDataLine ----------------------
//----------------------------------------------------


//----------------------------------------------------
//--- これ以降は TargetDataLine ----------------------
//----------------------------------------------------

/*
 * Class:     gnu_javax_sound_sampled_wce_WCETargetDataLine
 * Method:    openNative
 * Signature: (IFIIIFZ)I
 */
JNIEXPORT jint JNICALL Java_gnu_javax_sound_sampled_wce_WCETargetDataLine_openNative
						(JNIEnv *env,
						 jobject instance,
						 jint bufferSize,
						 jfloat sampleRate,
						 jint sampleSizeInBits,
						 jint channels,
						 jint frameSize,
						 jfloat frameRate,
						 jboolean bigEndian) {
	int i;
	MMRESULT mmresult;

	// wavein_dataを確保する
	wavein_data* data = get_wavein_data(NULL);
	if (! data) {
		throw_LineUnavailableException(env, "Too many wavein_data");
		return 0;
	}
	
	// 確保した構造体の内容を初期化する
	InitializeCriticalSection(&data->lock);
	data->hHeaderEvent = CreateEvent(NULL,
									 FALSE,
									 FALSE,
									 NULL);
	data->instance = instance;

	// フォーマットの内容から、WAVEFORMATEX構造体を初期化する
	// フォーマットの内容から、WAVEFORMATEX構造体を初期化する
	init_waveformat(&data->waveformat,
					(int) sampleRate,
					sampleSizeInBits,
					channels,
					frameSize,
					(int) frameRate);

	// バッファサイズを決定する
	data->max_buffer_size = adjust_max_buffer_size(bufferSize, &data->waveformat);

	// バッファを確保する
	if (! alloc_header_data(data->max_buffer_size, data->headers)) {
		close_waveout_data(data);
		throw_LineUnavailableException(env, "Not enough memory");
		return 0;
	}

	// 入力デバイスをオープンする
	mmresult = waveInOpen(&data->hWaveIn,
						   WAVE_MAPPER,
						   &data->waveformat,
						   (DWORD) g_hwndCallback,
						   0,
						   CALLBACK_WINDOW);
	if (mmresult != MMSYSERR_NOERROR) {
		// エラー処理
		close_wavein_data(data);
		throw_MMRESULT_Exception(env, mmresult);
		return 0;
	}

	// WAVEHDRを準備状態にする
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		mmresult = waveInPrepareHeader(data->hWaveIn,
						&data->headers[i],
						sizeof(WAVEHDR));
		if (mmresult != MMSYSERR_NOERROR) {
			waveInClose(data->hWaveIn);
			close_wavein_data(data);
			throw_MMRESULT_Exception(env, mmresult);
			return 0;
		}
	}

	// 先頭のヘッダにマークをつける
	data->headers[0].dwUser |= READ_HEAD_FLAG;

	// システムキューに入れる
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		mmresult = waveInAddBuffer(data->hWaveIn,
								   &data->headers[i],
								   sizeof(WAVEHDR));
		if (mmresult != MMSYSERR_NOERROR) {
			waveInClose(data->hWaveIn);
			close_wavein_data(data);
			throw_MMRESULT_Exception(env, mmresult);
			return 0;
		}
	}

	return (jint) data;
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCETargetDataLine
 * Method:    readNative
 * Signature: (I[BII)I
 */
JNIEXPORT jint JNICALL Java_gnu_javax_sound_sampled_wce_WCETargetDataLine_readNative
(JNIEnv *env, jobject instance, jint nativeData, jbyteArray b, jint off, jint len) {
	wavein_data* data = (wavein_data*) nativeData;
	int buff_left;
	char* p;
	char* org_p;
	char* end_p;

	if (! data) {
		return 0;
	}

	// 指定されたバイト長を、バッファのアラインに合わせる
	len -= (len % data->waveformat.nBlockAlign);
	if (len <= 0) {
		return 0;
	}
	buff_left = len;
	p = (char*) env->GetByteArrayElements(b, NULL);
	if (! p) {
		return 0;
	}
	org_p = p;
	end_p = p + len;

	// コピー開始位置
	p += off;

	// 指定されたバイト数分コピーするまで、処理を繰り返す
	while (p < end_p) {
		int index;
		DWORD bytes_remains;
		WAVEHDR* hdr;
		DWORD copy_len;
		MMRESULT mmresult;
		BOOL active;

		while ((index = lock_readable_header(data)) < 0) {
			// ヘッダ内のデータが読み取り可能になるまで待機する

			if (WaitForSingleObject(data->hHeaderEvent, INFINITE) != WAIT_OBJECT_0) {
				// 待機中にエラー発生
				// throw_RuntimeException(env, "WaitForSingleObject() failed");
				goto END;
			}

			if (data->stopped || data->closing) {
				// 待機中にstop()された
				// ToDo: flush()された場合にもすぐにリターンするように修正する
				break;
			}
		}
		if (data->stopped || data->closing) {
			// 停止された場合はすぐにリターンする
			// ToDo: flush()された場合にもすぐにリターンするように修正する
			goto END;
		}

		assert (0 <= index && index < WAVEHDR_COUNT);
		
		// この時点でアクティブかを調べる
		active = is_wavein_active(data);

		// 読み出し可能なWAVEHDRを得る
		hdr = &data->headers[index];

		// データをバッファにコピーする
		bytes_remains = GET_READ_REMAINS(hdr->dwUser);
		assert(bytes_remains <= hdr->dwBytesRecorded);
		copy_len = (p + bytes_remains >= end_p)
				? (end_p - p)
				: bytes_remains;
		memcpy(p,
		       hdr->lpData + (hdr->dwBytesRecorded - bytes_remains),
		       copy_len);

		
		// 読み出しバッファをアンロックする
		bytes_remains -= copy_len;
		unlock_readable_header(data,
				       index,
				       bytes_remains);
		if ((bytes_remains <= 0)
				&& (! data->closing)) {
			// 全てのデータを読み込んだ場合は、
			// 再度入力キューに入れなおす
			mmresult = waveInAddBuffer(data->hWaveIn, hdr, sizeof(WAVEHDR));
			if (mmresult != MMSYSERR_NOERROR) {
				// エラー発生
				throw_MMRESULT_Exception(env, mmresult);
				goto END;
			}
		}
		
		// アクティブ状態に移行した場合、STARTイベントを送る
		if (! active) {
			post_LineEvent(env,
					 data->instance,
					 g_LineEvent_Type_START,
					 AudioSystem_NOT_SPECIFIED);
		}

		// コピー位置を更新する
		p += copy_len;
	}
END:
	env->ReleaseByteArrayElements(b, (jbyte*) org_p, 0);
	// バッファにコピーしたバイト数を返す
	return p - org_p;
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCETargetDataLine
 * Method:    closeNative
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_gnu_javax_sound_sampled_wce_WCETargetDataLine_closeNative
(JNIEnv *env, jobject instance, jint nativeData) {
	int i;
	MMRESULT mmresult;
	wavein_data* data = (wavein_data*) nativeData;
	if (! data) {
		return;
	}

	EnterCriticalSection(&data->lock);
	data->closing = TRUE;
	LeaveCriticalSection(&data->lock);
	
	// waveInReset(), waveInUnprepareHeader(), waveInClose()の順に呼び出す
	mmresult = waveInReset(data->hWaveIn);
	SetEvent(data->hHeaderEvent);

	if (mmresult == MMSYSERR_NOERROR) {
		for (i = 0; i < WAVEHDR_COUNT; ++i) {
			mmresult = waveInUnprepareHeader(data->hWaveIn, &data->headers[i], sizeof(WAVEHDR));
		}
		mmresult = waveInClose(data->hWaveIn);
	}

	if (mmresult != MMSYSERR_NOERROR) {
		throw_MMRESULT_Exception(env, mmresult);
	}
}
/*
 * Class:     gnu_javax_sound_sampled_wce_WCETargetDataLine
 * Method:    drainNative
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_gnu_javax_sound_sampled_wce_WCETargetDataLine_drainNative
(JNIEnv *env, jobject instance, jint nativeData) {
	wavein_data* data = (wavein_data*) nativeData;
	if (! data) {
		return;
	}

	// １つ以上のWAVEHDRが読み出し可能になるまでじっと待つ
	for (;;) {
		int i;
		BOOL readable_buffer_exists = FALSE;

		EnterCriticalSection(&data->lock);
		for (i = 0; i < WAVEHDR_COUNT; ++i) {
			// 読み出し可能なバッファがあるか調べる
			if (data->headers[i].dwUser & READABLE_FLAG) {
				readable_buffer_exists = TRUE;
				break;
			}
		}
		LeaveCriticalSection(&data->lock);

		if (readable_buffer_exists) {
			// 読み出し可能なバッファが1つ以上ある
			break;
		}

		// イベントが発生するまで待機する
		if (WaitForSingleObject(data->hHeaderEvent, INFINITE) != WAIT_OBJECT_0) {
			// 待機中にエラー発生
			throw_RuntimeException(env, "WaitForSingleObject() failed.");
			break;
		}
	}
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCETargetDataLine
 * Method:    flushNative
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_gnu_javax_sound_sampled_wce_WCETargetDataLine_flushNative
(JNIEnv *env, jobject instance, jint nativeData) {
	wavein_data* data = (wavein_data*) nativeData;
	if (! data) {
		return;
	}

	// リセットフラグを立てる
	EnterCriticalSection(&data->lock);
	data->reset = TRUE;
	LeaveCriticalSection(&data->lock);

	// システムにバッファのリセットを指示する
	waveInReset(data->hWaveIn);

	// この後、非同期にMM_WIM_DATA メッセージが来る
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCETargetDataLine
 * Method:    isNativeActive
 * Signature: (I)Z
 */
JNIEXPORT jboolean JNICALL Java_gnu_javax_sound_sampled_wce_WCETargetDataLine_isNativeActive
(JNIEnv *env, jobject instance, jint nativeData) {
	wavein_data* data = (wavein_data*) nativeData;
	if (! data) {
		return JNI_FALSE;
	}

	return (jboolean) is_wavein_active(data);
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCETargetDataLine
 * Method:    getNativeBufferSize
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_gnu_javax_sound_sampled_wce_WCETargetDataLine_getNativeBufferSize
(JNIEnv *env, jobject instance, jint nativeData) {
	wavein_data* data = (wavein_data*) nativeData;
	if (! data) {
		return AudioSystem_NOT_SPECIFIED;
	}
	return (jint) data->max_buffer_size;
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCETargetDataLine
 * Method:    availableNative
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_gnu_javax_sound_sampled_wce_WCETargetDataLine_availableNative
(JNIEnv *env, jobject instance, jint nativeData) {
	wavein_data* data = (wavein_data*) nativeData;
	int i;
	jint size = 0;
	if (! data) {
		return 0;
	}

	EnterCriticalSection(&data->lock);
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		DWORD dwUser = data->headers[i].dwUser;
		if (dwUser & (READABLE_FLAG | READ_HEAD_FLAG)) {
			size = GET_READ_REMAINS(data->headers[i].dwUser);
			break;
		}
	}
	return size;
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCETargetDataLine
 * Method:    startNative
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_gnu_javax_sound_sampled_wce_WCETargetDataLine_startNative
(JNIEnv *env, jobject instance, jint nativeData) {
	wavein_data* data = (wavein_data*) nativeData;
	if (! data) {
		return;
	}

	EnterCriticalSection(&data->lock);
	data->stopped = FALSE;
	data->reset = FALSE;;
	LeaveCriticalSection(&data->lock);

	// 音声入力を開始する
	waveInStart(data->hWaveIn);

	// 再生中のバッファが存在する場合、STARTイベントを送信する
	if (is_wavein_active(data)) {
		post_LineEvent(env,
				 data->instance,
				 g_LineEvent_Type_START,
				 AudioSystem_NOT_SPECIFIED);
	}
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCETargetDataLine
 * Method:    stopNative
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_gnu_javax_sound_sampled_wce_WCETargetDataLine_stopNative
(JNIEnv *env, jobject instance, jint nativeData)
{
  wavein_data* data = (wavein_data*) nativeData;
  if (! data)
  {
    return;
  }

  EnterCriticalSection(&data->lock);
  data->stopped = TRUE;
  data->reset = FALSE;
  LeaveCriticalSection(&data->lock);

  // 音声入力をストップする
  waveInStop(data->hWaveIn);

  // 待機しているスレッドがいるかもしれないので、
  // イベントを通知する
  SetEvent(data->hHeaderEvent);

  // STOPイベントを送信する
  post_LineEvent(env,
		 data->instance,
		 g_LineEvent_Type_STOP,
		 AudioSystem_NOT_SPECIFIED);
}
