/**************************************************************************
 * Copyright (C) 2008 Cocha                                               *
 * http://sourceforge.jp/projects/ecodecotool/                            *
 *                                                                        *
 *  This Program is free software; you can redistribute it and/or modify  *
 *  it under the terms of the GNU General Public License as published by  *
 *  the Free Software Foundation; either version 2, or (at your option)   *
 *  any later version.                                                    *
 *                                                                        *
 *  This Program is distributed in the hope that it will be useful,       *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the          *
 *  GNU General Public License for more details.                          *
 *                                                                        *
 *  You should have received a copy of the GNU General Public License     *
 *  along with GNU Make; see the file COPYING.  If not, write to          *
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. *
 *                                                                        *
 **************************************************************************/

#include "SourceFlac.h"


CSourceFlac::CSourceFlac(IUnknown *pUnk, HRESULT *phr)
   : CSimplePushSource(L"Source", pUnk, CLSID_SourceFlac, phr)
{  

   
   ::ZeroMemory(m_awInputFileName, sizeof(m_awInputFileName));
   m_pDecoder = NULL;
   m_nMaxBlockSize = 0;
   m_llDuration = 0;
   m_pOutBufferPointer = NULL;
   m_nOutBufferLength = NULL;
   m_rtCurrent = 0;
   m_rtStop = INFINITE_SECOND;
}

CSourceFlac::~CSourceFlac()  
{  

   
   if(m_pDecoder != NULL)
   {
   	FLAC__stream_decoder_delete(m_pDecoder);
      m_pDecoder = NULL;
   }
}  

void CSourceFlac::metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data)
{
   if(metadata->type == FLAC__METADATA_TYPE_STREAMINFO)
   {
      ((CSourceFlac *)(client_data))->m_outFormat.nSamplesPerSec = (DWORD)metadata->data.stream_info.sample_rate;
      ((CSourceFlac *)(client_data))->m_outFormat.wBitsPerSample = (WORD)metadata->data.stream_info.bits_per_sample;
      ((CSourceFlac *)(client_data))->m_outFormat.nChannels = (WORD)metadata->data.stream_info.channels;

      ((CSourceFlac *)(client_data))->m_nMaxBlockSize = metadata->data.stream_info.max_blocksize;

      ((CSourceFlac *)(client_data))->m_llDuration = (LONGLONG)metadata->data.stream_info.total_samples * ONE_SECOND / (LONGLONG)metadata->data.stream_info.sample_rate;
   }
}

FLAC__StreamDecoderWriteStatus CSourceFlac::write_callback(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void *client_data) 
{
   CSourceFlac *pMyClass = (CSourceFlac *)client_data;

   int i, j;
   int nBlockSize = (int)frame->header.blocksize;
   int nChannels = (int)frame->header.channels;

   if(frame->header.bits_per_sample == 16)
   {
      short *ps = (short *)pMyClass->m_pOutBufferPointer;

      for(i=0;i<nBlockSize;i++)
      {
         for(j=0;j<nChannels;j++)
         {
            *ps = buffer[j][i];
            ps++;
         }
      }
   }
   else if(frame->header.bits_per_sample == 8)
   {
      BYTE *pb = pMyClass->m_pOutBufferPointer;

      for(i=0;i<nBlockSize;i++)
      {
         for(j=0;j<nChannels;j++)
         {
            *pb = buffer[j][i] + 0x80;
            pb++;
         }
      }
   }
   else if(frame->header.bits_per_sample == 24)
   {
      struct INT24
      {
         BYTE b[3];
      };

      INT24 *pn24 = (INT24 *)((CSourceFlac *)(client_data))->m_pOutBufferPointer;

      for(i=0;i<nBlockSize;i++)
      {
         for(j=0;j<nChannels;j++)
         {
            *pn24 = *(INT24 *)(&buffer[j][i]);
            pn24++;
         }
      }
   }
   else
      return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;

   pMyClass->m_nOutBufferLength += (frame->header.blocksize * frame->header.channels * frame->header.bits_per_sample / 8);

	return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}

void CSourceFlac::error_callback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) 
{

}

HRESULT CSourceFlac::FillBuffer(IMediaSample *pOutSample)
{  

   if(m_rtCurrent >= m_rtStop)
   {  
      return S_FALSE;
   }

   REFERENCE_TIME rtEnd;
   BYTE *pbOutBuffer = NULL;
   pOutSample->GetPointer(&pbOutBuffer);

   
   m_nOutBufferLength = 0;
   m_pOutBufferPointer = pbOutBuffer;

   FLAC__stream_decoder_process_single(m_pDecoder);

   if(m_nOutBufferLength == 0)
      return S_FALSE;

   
   rtEnd = m_rtCurrent + (LONGLONG)m_nOutBufferLength * ONE_SECOND / m_outFormat.nAvgBytesPerSec;

   if(rtEnd >= m_rtStop)
   {  

      m_nOutBufferLength = m_nOutBufferLength - (int)((rtEnd - m_rtStop) * m_outFormat.nAvgBytesPerSec / ONE_SECOND);
      rtEnd = m_rtStop;

      if(m_nOutBufferLength < 0)
         m_nOutBufferLength = 0;
   }

   
   pOutSample->SetTime(&m_rtCurrent, &rtEnd);
   pOutSample->SetActualDataLength(m_nOutBufferLength);
   pOutSample->SetSyncPoint(FALSE);

   
   m_rtCurrent = rtEnd;

   return S_OK;
}

HRESULT CSourceFlac::OnSeek(LONGLONG llNewPosition, LONGLONG llStopPosition)
{  

   if(m_pDecoder == NULL)
      return E_NOTIMPL;

   if(llNewPosition >= 0)
   {  

      
      int nBlockOffset = (int)(llNewPosition * m_outFormat.nSamplesPerSec / ONE_SECOND);
      FLAC__stream_decoder_seek_absolute(m_pDecoder, nBlockOffset);

      m_rtCurrent = (REFERENCE_TIME)llNewPosition;
   }

   if(llStopPosition >= 0)
   {  
      m_rtStop = (REFERENCE_TIME)llStopPosition;
   }

   return S_OK;
}

HRESULT CSourceFlac::OnConnectOutPin(CMediaType *pmtOut, int *pnOutBufferSize, REFERENCE_TIME *pTotalMediaTime)
{  

   if(m_pDecoder == NULL)
      return E_FAIL;

   
   FLAC__stream_decoder_reset(m_pDecoder);

   
   ::ZeroMemory(&m_outFormat, sizeof(m_outFormat));

   FLAC__stream_decoder_process_until_end_of_metadata(m_pDecoder);
   m_outFormat.cbSize = 0;
   m_outFormat.wFormatTag = WAVE_FORMAT_PCM;
   m_outFormat.nBlockAlign = m_outFormat.nChannels * (m_outFormat.wBitsPerSample / 8);
   m_outFormat.nAvgBytesPerSec = m_outFormat.nSamplesPerSec * m_outFormat.nBlockAlign;

   if(m_outFormat.nAvgBytesPerSec == 0 || m_nMaxBlockSize == 0)
      return E_FAIL;

   
   pmtOut->SetType(&MEDIATYPE_Audio);
   pmtOut->SetSubtype(&MEDIASUBTYPE_PCM);
   pmtOut->SetTemporalCompression(FALSE);
   pmtOut->SetSampleSize(0);
   pmtOut->SetFormatType(&FORMAT_WaveFormatEx);
   pmtOut->SetFormat((BYTE*)&m_outFormat, sizeof(m_outFormat));

   
   *pnOutBufferSize = m_nMaxBlockSize * m_outFormat.nBlockAlign * 12 / 10;

   
   *pTotalMediaTime = (REFERENCE_TIME)m_llDuration;

   return S_OK;
}

STDMETHODIMP CSourceFlac::OnQueryInterface(REFIID riid, void ** ppv)
{
   if(riid == IID_IFileSourceFilter)
      return GetInterface((IFileSourceFilter *)this, ppv);

   return CSource::NonDelegatingQueryInterface(riid, ppv);
}

CUnknown * WINAPI CSourceFlac::CreateInstance(LPUNKNOWN punk, HRESULT *phr)
{  

   ASSERT(phr);
    
   CSourceFlac *pNewObject = new CSourceFlac(punk, phr);
   if (pNewObject == NULL)
   {
      if(phr != NULL)
         *phr = E_OUTOFMEMORY;
   }

   return dynamic_cast<CUnknown *>(pNewObject);
}

STDMETHODIMP CSourceFlac::Load(LPCOLESTR pszFileName, const AM_MEDIA_TYPE *pmt)
{  

   if(::PathFileExists(pszFileName) == FALSE)
      return E_FAIL;

   
   if(m_pDecoder != NULL)
   {
   	FLAC__stream_decoder_delete(m_pDecoder);
      m_pDecoder = NULL;
   }

   
   char szFileName[MAX_PATH];
   ::WideCharToMultiByte(CP_ACP, 0, pszFileName, -1, szFileName, MAX_PATH, NULL, NULL);

   
   FLAC__StreamDecoderInitStatus status;

   m_pDecoder = FLAC__stream_decoder_new();
   if(m_pDecoder == NULL)
      return E_FAIL;

	FLAC__stream_decoder_set_md5_checking(m_pDecoder, false);
	FLAC__stream_decoder_set_metadata_respond_all(m_pDecoder);

   status = FLAC__stream_decoder_init_file(m_pDecoder, szFileName, write_callback, metadata_callback, error_callback, this);

   if(status != FLAC__STREAM_DECODER_INIT_STATUS_OK)
   {
   	FLAC__stream_decoder_delete(m_pDecoder);
      m_pDecoder = NULL;

      return E_FAIL;
   }

   
   ::lstrcpy(m_awInputFileName, pszFileName);
   return S_OK;
}

STDMETHODIMP CSourceFlac::GetCurFile(LPOLESTR *ppszFileName, AM_MEDIA_TYPE *pmt)
{  

   CheckPointer(ppszFileName, E_POINTER);

   ::lstrcpy(*ppszFileName, m_awInputFileName);

   if(pmt != NULL)
   {
      ::ZeroMemory(pmt, sizeof(*pmt));
      pmt->majortype = MEDIATYPE_NULL;
      pmt->subtype = MEDIASUBTYPE_NULL;
   }

   return S_OK;
}

