#include <mof/sound/sound_recoder.hpp>
#include <mof/sound/CWaveFile.h>
#include <mof/sound/DirectSoundDevice.hpp>
#include <boost/thread/thread.hpp>
#include <mof/ConsoleIO.hpp>


namespace mof
{
namespace sound
{
//{{{ impl
	struct sound_recoder::impl
	{
		CWaveFile file_;
		size_t capture_offset_;
		size_t buffer_size_;
		double interval_sec_;
		LPDIRECTSOUNDCAPTUREBUFFER8 cap_buf_;
		LPDIRECTSOUNDCAPTURE8 cap_;
		boost::thread thread_;
		boost::mutex mtx_;
		bool finalize_;///< recordスレッドを終了させる際にtrueにする

		impl()
			: capture_offset_(0), cap_(NULL), cap_buf_(NULL), finalize_(false), interval_sec_(1.0)
		{
		}

		~impl()
		{
			if (cap_buf_) cap_buf_->Release();
			if (cap_) cap_->Release();
		}
//{{{ record
		void record()
		{
			HRESULT hr;
			void* pbCaptureData = NULL;
			DWORD dwCaptureLength;
			void* pbCaptureData2 = NULL;
			DWORD dwCaptureLength2;

 			UINT  dwDataWrote;
			DWORD dwReadPos;
			LONG lLockSize;
 
  			if (FAILED (hr = cap_buf_->GetCurrentPosition(NULL, &dwReadPos))) {
				throw std::runtime_error("failed to get current position");
			}
 
			// プライベート カーソルと読み込みカーソルの間にある部分をすべてロックし、
			// ラップ アラウンドに備える。
  			lLockSize = dwReadPos - capture_offset_;
  			if( lLockSize < 0 ) lLockSize += buffer_size_;
			if (lLockSize == 0) return;
 
  			if (FAILED(hr = cap_buf_->Lock( 
        		capture_offset_, lLockSize, 
        		&pbCaptureData, &dwCaptureLength, 
        		&pbCaptureData2, &dwCaptureLength2, 0L))){
				throw std::runtime_error("failed to lock");
			}
 
			// データを書き込む。ラップ アラウンドを考慮して、
			// この処理は 2 つのステップで行われる。

  			if (FAILED( hr = file_.write( dwCaptureLength, (BYTE*)pbCaptureData, &dwDataWrote))) {
				throw std::runtime_error("failed to write");
			}
 
  			if (pbCaptureData2 != NULL) {
    			if (FAILED(hr = file_.write(dwCaptureLength2, (BYTE*)pbCaptureData2, &dwDataWrote))) {
					throw std::runtime_error("failed to write2");
				}
			}
 
 			// キャプチャ バッファをアンロックする。
 			cap_buf_->Unlock( pbCaptureData, dwCaptureLength, pbCaptureData2, dwCaptureLength2);
  
  			// キャプチャ オフセットを前方に移動する。
  			capture_offset_ += dwCaptureLength; 
  			capture_offset_ %= buffer_size_; 
  			capture_offset_ += dwCaptureLength2; 
  			capture_offset_ %= buffer_size_; 
			
		}
//}}}
	};
//}}}
//{{{ constructor
	sound_recoder::sound_recoder()
		: pimpl_(new impl())
	{
	}
	
	sound_recoder::sound_recoder(const tstring& filename)
		: pimpl_(new impl())
	{
		open(filename);
	}
//}}}
//{{{ destructor
	sound_recoder::~sound_recoder()
	{
		{// スレッド終了準備
			boost::mutex::scoped_lock lk(pimpl_->mtx_);
			pimpl_->finalize_ = true;
		}
		pimpl_->thread_.join();
		pimpl_->file_.close();
	}
//}}}
//{{{ open
	void sound_recoder::open(const tstring& filename)
	{
		WAVEFORMATEX format;

		ZeroMemory(&format, sizeof(format));
		format.wFormatTag = WAVE_FORMAT_PCM;
		format.nSamplesPerSec = 22050;
		format.wBitsPerSample = 16;
		format.nChannels = 2;
		format.nBlockAlign = format.nChannels * (format.wBitsPerSample / 8);
		format.nAvgBytesPerSec = format.nBlockAlign * format.nSamplesPerSec;
		pimpl_->buffer_size_ = format.nAvgBytesPerSec * pimpl_->interval_sec_;
		
		if(FAILED(pimpl_->file_.open(const_cast<LPTSTR>(filename.c_str()), &format, WAVEFILE_WRITE))) {
			pimpl_->file_.close();
			throw std::runtime_error(mof::tstring("failed to open wave file to write:") + filename);
		}

		pimpl_->capture_offset_ = 0;

		{// キャプチャデバイスの生成
			if(FAILED(DirectSoundCaptureCreate8(NULL, &(pimpl_->cap_), NULL))) {
				throw std::runtime_error("failed to create sound capture");
			}
		
			DSCBUFFERDESC desc;
			ZeroMemory(&desc, sizeof(desc));
			desc.dwSize = sizeof(desc);
			desc.dwFlags = 0;
			desc.dwBufferBytes = pimpl_->buffer_size_;
			desc.dwReserved = 0;
			desc.lpwfxFormat = &format;
			desc.dwFXCount = 0;
			LPDIRECTSOUNDCAPTUREBUFFER cap_buf = NULL;
			if(FAILED(pimpl_->cap_->CreateCaptureBuffer(&desc, &cap_buf, NULL))) {
				throw std::runtime_error("failed to create capture buffer");
			}
			
			if(FAILED(cap_buf->QueryInterface(IID_IDirectSoundCaptureBuffer8, reinterpret_cast<LPVOID*>(&pimpl_->cap_buf_)))) {
				cap_buf->Release();
				throw std::runtime_error("failed to query interface for IID_IDirectSoundCaptureBuffer8");
			}
			cap_buf->Release();// cap_bufはもう解放してもよい
		}
		pimpl_->cap_buf_->Start(DSCBSTART_LOOPING);

		// キャプチャを開始
		pimpl_->thread_ = boost::thread([this](){
				while (true) {
					Sleep(pimpl_->interval_sec_ * 900);// 少し少なめに待つ
					try {
						pimpl_->record();
					} catch (std::exception& e) {
						DEBUG_PRINT(mof::tstring("sound record:") + e.what());
					}
					{// 終了判定
						boost::mutex::scoped_lock lk(pimpl_->mtx_);
						if (this->pimpl_->finalize_) return; 
					}
				}
			});

	}
//}}}
}// namespace sound
}// namespace mof

