/*
-----------------------------------------------------------------------------
This source file is part of OpenSpace3D
For the latest info, see http://www.openspace3d.com

Copyright (c) 2010 I-maginer

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 2 of the License, 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA, or go to
http://www.gnu.org/copyleft/lesser.txt

You may alternatively use this source under the terms of a specific version of
the OpenSpace3D Unrestricted License provided you have obtained such a license from
I-maginer.
-----------------------------------------------------------------------------
*/

/*! \class Recognition
  *  \brief Management of the recognition class
  *  \
  *  \version 1.0
  *  \date mai  2010
  */


//! utils libraries
#include "reco.h"

//! Recognition Constructor
Recognition::Recognition()
{
	m_bGotReco = FALSE;
  m_bInSound = FALSE;
}


//! Recognition Destructor
Recognition::~Recognition()
{
	close();
}


//! Add word to recognition
void Recognition::AddWord(char *s_Rule, char *s_Word)
{
  // Declare local identifiers:
	HRESULT hr = S_OK;
  SPSTATEHANDLE hStateTravel;

  wchar_t* w_Rule = convertCharToLPCWSTR(s_Rule);

  // Retrieve grammar rule's initial state
  hr = cpRecoGrammar->GetRule(w_Rule, 0, SPRAF_TopLevel | SPRAF_Active, TRUE, &hStateTravel); 
  SAFE_DELETE(w_Rule);

  wchar_t* w_Word = convertCharToLPCWSTR(s_Word);
  
  // Add a word to the grammar 
  hr = cpRecoGrammar->AddWordTransition(hStateTravel, NULL, w_Word, L" ", SPWT_LEXICAL, 1, NULL);
  SAFE_DELETE(w_Word);

  // Update the SR engine's language model
	hr = cpRecoGrammar->Commit(NULL);

  // Enable the grammar to let applications receive notifications about recognition
  hr = cpRecoGrammar->SetGrammarState(SPGS_ENABLED);

  // Activate a new rule
  // $AS: A rule should be specified by its name, and the second parameter must always be set to NULL
	hr = cpRecoGrammar->SetRuleState(NULL, NULL, SPRS_ACTIVE);
}


//! Init Recognition Objects
bool Recognition::initializeObjects()
{
  HRESULT hr;

  // Initialize a new instance of Speech Recognition engine
  hr = pRecog.CoCreateInstance(CLSID_SpInprocRecognizer);
  if (FAILED(hr)) 
		MMechostr(MSKDEBUG,"Error: Can't create SAPI Speech Recognizer (ISpRecognizer)\n") ;
      
  if (SUCCEEDED(hr))
  {
    // Create a new context of SR engine
		hr = pRecog->CreateRecoContext(&cpRecoContext);
  }
  
  if (hr != S_OK)
  {
    MMechostr(MSKDEBUG,"Error: Cannot create SAPI Recognition Context (ISpRecoContext)");
    return false;
  }

	if (SUCCEEDED(hr))
  {
    // Set up the SR instance to send notifications
		hr = cpRecoContext->SetNotifyCallbackFunction(&sapi_callback, 0, LPARAM(this));
  }
  
  if (hr != S_OK)
  {
    MMechostr(MSKDEBUG,"Error: Cannot set notify callback function. (SetNofifyCallbackFunction)");
    return false;
  }
     
	if (SUCCEEDED(hr))
	{
    // Set which type of events the engine must be notified of
    // If SetInterest() is not called, the SR engine defaults to SPEI_RECOGNITION as the only event interest
    const ULONGLONG ullInterest = SPFEI(SPEI_SOUND_START) |         // Audible sound is available through the input stream
                                  SPFEI(SPEI_SOUND_END) |           // Audible sound is no longer available, or sound stream has been inactive for a while
                                  SPFEI(SPEI_RECOGNITION);          // A full recognition has been returned
		hr = cpRecoContext->SetInterest(ullInterest, ullInterest);
	}

  if (hr != S_OK)
     MMechostr(MSKDEBUG,"Error: Cannot correctly set notifications for the Speech Recognizer");
		
  // Create an object token of AudioInput type (mic)
	hr = SpCreateDefaultObjectFromCategoryId(SPCAT_AUDIOIN, &cpAudio);	
	if (FAILED(hr)) 
  {
		MMechostr(MSKDEBUG,"Error: Can't create default audio object\n") ;
    return false;
  }
	else
	{
    // Specify the input stream which must be used by the SR engin (in our case, audio input from mic)
		hr = pRecog->SetInput(cpAudio, TRUE); 

    // Enable the speech recognition engine
		hr = pRecog->SetRecoState(SPRST_ACTIVE);
	}
		
	if (SUCCEEDED(hr))
  {
    // Create a new grammar object
		hr = cpRecoContext->CreateGrammar(1, &cpRecoGrammar);
  }

  if (hr != S_OK)
  {
    MMechostr(MSKDEBUG,"Error: Failed to create grammar\n");
    return false;
  }

	if (SUCCEEDED(hr))
  {
    // Load a dictation topic into the SR engine
    // The grammar is loaded statically (rules can't be modified in real-time)
    hr = cpRecoGrammar->LoadDictation(NULL, SPLO_STATIC);
  }

  if (SUCCEEDED(hr))
  {
    // Enable the dictation topic to be notified of recognitions
    hr = cpRecoGrammar->SetDictationState(SPRS_ACTIVE);
  }

	hr = cpAudio->SetVolumeLevel(1000);
	if (FAILED(hr))
    MMechostr(MSKDEBUG,"Error: volume reco\n") ;

  return (hr == S_OK);
}


//! Defines the SAPI callback
void _stdcall Recognition::sapi_callback(WPARAM wParam, LPARAM lParam)
{
  Recognition* pThis = (Recognition*)lParam;
  pThis->callbackEventReco();
}


//! Close the recognition instance
void Recognition::close()
{
  // Release objects for SR engine, SR context and dictation topic
  pRecog.Release();
  cpRecoContext.Release();
  cpRecoGrammar.Release();
}


//! Recognition Event Callback
void Recognition::callbackEventReco()
{
  USES_CONVERSION;
  CSpEvent evt;
  BYTE bDisplayAttr;

  // Clear the current instance, and retrieve the next event from the message queue
  while (evt.GetFrom(cpRecoContext) == S_OK)
  {
    switch (evt.eEventId)
    {
      case SPEI_SOUND_START:
      {
        m_bInSound = TRUE;
        PostMessage(HScol, RECOGNITION_START_CB, (int)this,(LPARAM)NULL);
      }
      break;

      case SPEI_SOUND_END:
      {
        if (m_bInSound)
        {
          m_bInSound = FALSE;
          PostMessage(HScol, RECOGNITION_END_CB, (int)this,(LPARAM)NULL);
          m_bGotReco = FALSE;
        }
      }
      break;
      
      case SPEI_RECOGNITION:
      {
        HRESULT hr = S_OK;
        const USHORT MY_MAX_ALTERNATES = 10;
        CComPtr<ISpPhraseAlt> pcpPhraseAlt[MY_MAX_ALTERNATES];
        SPPHRASE* pPhrase;
        CSpDynamicString betterResult;
        float ConfidenceMax = 0.0;
        ULONG ulCount;
        std::list <pTextRec> lPhraseRec;
        std::list <char*> lWordsRec;

        // Retrieve information about the recognized phrase
        hr = evt.RecoResult()->GetPhrase(&pPhrase);
        if (SUCCEEDED(hr))
        {
          // Retrieve a list of MY_MAX_ALTERNATES alternative phrases related to the recognized phrase
          hr = evt.RecoResult()->GetAlternates(pPhrase->Rule.ulFirstElement,
                                               pPhrase->Rule.ulCountOfElements,
                                               MY_MAX_ALTERNATES,
                                               (ISpPhraseAlt**) pcpPhraseAlt,
                                               &ulCount);
        }
        if (SUCCEEDED(hr))
        {
          // Browse the list of alternative phrases in order of highest likelyhood with the original phrase
          for (int i = 0; i < ulCount; i++)
          {
            SPPHRASE* pPhraseAlt;
            CSpDynamicString pwszAlternate;

            // Retrieve information about the current alternative phrase
            pcpPhraseAlt[i]->GetPhrase(&pPhraseAlt);

            // Get the phrase's entire text string
            hr = pcpPhraseAlt[i]->GetText(SP_GETWHOLEPHRASE, SP_GETWHOLEPHRASE, TRUE, &pwszAlternate, NULL);
            if (SUCCEEDED(hr))
            {
              float confidence = pPhraseAlt->pElements->SREngineConfidence;
              pTextRec paramTextRec;
              paramTextRec.altText = pwszAlternate;
              paramTextRec.altConfidence = confidence;
              lPhraseRec.push_back(paramTextRec);
              if (ConfidenceMax < confidence)
              {
                ConfidenceMax = confidence;
                betterResult = pwszAlternate;
              }
            }
          }
        }

        cbData* cbdataTextAlt = new cbData(lPhraseRec);
        PostMessage(HScol, RECOGNITION_TEXTS_ALT_CB, (int)this,(LPARAM)cbdataTextAlt);

        cbData* cbdataText = new cbData(betterResult.CopyToChar());
        PostMessage(HScol, RECOGNITION_TEXT_CB, (int)this,(LPARAM)cbdataText);

        // $MS : TODO word recognition in speech
        //cbData * cbdataWords = new cbData(lWordsRec);
        //PostMessage(HScol, RECOGNITION_WORDS_CB, (int)this,(LPARAM)cbdataWords);
      }
      break;
    }
  }
}


//! Get The volume from recognition
int Recognition::getVolume()
{
	HRESULT hr = S_FALSE;
	int volume = -1;
	hr = cpAudio->GetVolumeLevel((ULONG *)&volume);
	if(FAILED(hr))
		return -1;
	else
		return volume / 100;
}


//! Set The volume for the recognition
void Recognition::setVolume(int volume)
{
	HRESULT hr = S_OK;
	hr = cpAudio->SetVolumeLevel((ULONG)(volume * 100));
	if(FAILED(hr )) MMechostr(MSKDEBUG,"Error: volume reco\n") ;
}
