/*
 * extb plugin to VDR (C++)
 *
 * (C) 2004 - 2006 Andreas Brachold <vdr04-at-deltab-dot-de>
 *
 * This code 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
 * of the License, or (at your option) any later version.
 *
 * This code 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
 */

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include "extboardio.h"
#include <vdr/menu.h>

/// Ctor, load default values
cExtBoardIO::cExtBoardIO()
{
  unsigned int n;
  for(n=0;n<memberof(m_eLastSendLEDState);++n) {
      m_eLastSendLEDState[n] = eLED_Unset;  
  }  
  for(n=0;n<memberof(m_nLastSendOutputState);++n) {
      m_nLastSendOutputState[n] = -1;  
  }  
  for(n=0;n<memberof(m_nCardIsRecording);++n) {
      m_nCardIsRecording[n] = 0;  
  }
  m_eReplay = eReplayNone;
  m_eDVDAudio = eDVDNone;
  m_eDVDVideo = eDVDVideoNone;
  m_nMP3 = 0;

  m_nChannel = 0;
  
  m_nLastVolume = cDevice::CurrentVolume();

  m_bCutting = false;

  m_nOsdMode = 0;
}

//ext. Output///////////////////////////////////////////////////////////////////  
const char * cExtBoardIO::m_szOutput_ON [OUT_CNT] = 
{ "S_OUT1","S_OUT2","S_OUT3","S_OUT4" };
const char * cExtBoardIO::m_szOutput_OFF[OUT_CNT] =
{ "R_OUT1","R_OUT2","R_OUT3","R_OUT4" };

//LED///////////////////////////////////////////////////////////////////////////  
const char * cExtBoardIO::m_szLED_ON [LED_CNT] = 
{
  "S_LD1","S_LD2","S_LD3", "S_LD4", "S_LD5", "S_LD6", "S_LD7",
  "S_LD8","S_LD9","S_LD10","S_LD11","S_LD12","S_LD13","S_LD14"
};
const char * cExtBoardIO::m_szLED_OFF[LED_CNT] =
{
  "R_LD1","R_LD2","R_LD3", "R_LD4", "R_LD5", "R_LD6", "R_LD7",
  "R_LD8","R_LD9","R_LD10","R_LD11","R_LD12","R_LD13","R_LD14"
};
const char * cExtBoardIO::m_szLED_FLASH[LED_CNT] =
{
  "B_LD1","B_LD2","B_LD3", "B_LD4", "B_LD5", "B_LD6", "B_LD7",
  "B_LD8","B_LD9","B_LD10","B_LD11","B_LD12","B_LD13","B_LD14"
};


bool cExtBoardIO::ShouldOn(int nModes) const
{
  unsigned int n;
  switch(nModes)
  {
  // Instant on or off
  case cExtbSetup::eIOModes_Off:             return false;
  case cExtbSetup::eIOModes_AlwaysOn:        return true;
  // Recording 
  case cExtbSetup::eIOModes_AnyRecording: {
    for(n=0;n<memberof(m_nCardIsRecording);++n) {
        if(0 != m_nCardIsRecording[n])
         return true; 
    }    
    return false;
  }
  case cExtbSetup::eIOModes_AllCardsRecording: {

    if(!cRecordControls::Active()) // No Recording activ
        return false;
    unsigned int r = 0,d = cDevice::NumDevices();
    for(unsigned int n=0;n<d && n<memberof(m_nCardIsRecording);++n) {
      if( (0 != m_nCardIsRecording[n]) // Device make current a Recording 
          || (!cDevice::GetDevice(n)->ProvidesSource(cSource::stNone))) // Device can't Receiving 
            ++r;
    }
    return r == d;
  }
  case cExtbSetup::eIOModes_RecordingOnCard1: return (0 != m_nCardIsRecording[0]);
  case cExtbSetup::eIOModes_RecordingOnCard2: return (0 != m_nCardIsRecording[1]);
  case cExtbSetup::eIOModes_RecordingOnCard3: return (0 != m_nCardIsRecording[2]);
  case cExtbSetup::eIOModes_RecordingOnCard4: return (0 != m_nCardIsRecording[3]);
  // Replay 
  case cExtbSetup::eIOModes_AnyReplay:        return m_eReplay != eReplayNone;
  case cExtbSetup::eIOModes_ReplayVDR:        return m_eReplay == eReplayVDR;
  case cExtbSetup::eIOModes_ReplayDVD:        return m_eReplay == eReplayDVD;
  case cExtbSetup::eIOModes_ReplayMP3:        return m_eReplay == eReplayMP3;
  // Channelfeature
  case cExtbSetup::eIOModes_ChannelHasCA:     return (m_eReplay == eReplayNone) && (m_nChannel & eHasCA);
  case cExtbSetup::eIOModes_ChannelHasDpid:   return (m_eReplay == eReplayNone) && (m_nChannel & eHasDpid);
  case cExtbSetup::eIOModes_ChannelHasApid:   return (m_eReplay == eReplayNone) && (m_nChannel & eHasApid);
  case cExtbSetup::eIOModes_ChannelHasTpid:   return (m_eReplay == eReplayNone) && (m_nChannel & eHasTpid);
  case cExtbSetup::eIOModes_ChannelHasVpid:   return (m_eReplay == eReplayNone) && (m_nChannel & eHasVpid);
  case cExtbSetup::eIOModes_ChannelIsRadio:   return (m_eReplay == eReplayNone) && (m_nChannel & eIsRadio);
  case cExtbSetup::eIOModes_ChannelSourceSat: return (m_eReplay == eReplayNone) && (m_nChannel & eIsSourceSat);
  case cExtbSetup::eIOModes_ChannelSourceCable:return (m_eReplay == eReplayNone) && (m_nChannel & eIsSourceCable);
  case cExtbSetup::eIOModes_ChannelSourceTerr:return (m_eReplay == eReplayNone) && (m_nChannel & eIsSourceTerr);
  case cExtbSetup::eIOModes_ChannelSatVPolar: return (m_eReplay == eReplayNone) && (m_nChannel & eIsSatVPolar);
  case cExtbSetup::eIOModes_ChannelSatHPolar: return (m_eReplay == eReplayNone) && (m_nChannel & eIsSatHPolar);
  // Audio state
  case cExtbSetup::eIOModes_AudioMute:        return IsMute();
  case cExtbSetup::eIOModes_MoreAudioTracks:  return false; //Funkioniert nicht ohne Scanthread return(cDevice::PrimaryDevice()->NumAudioTracks() > 1);
  // DVD Plugin Playbackmodes
  case cExtbSetup::eIOModes_DVD_AC3:          return m_eReplay == eReplayDVD && m_eDVDAudio == eDVDAC3;
  case cExtbSetup::eIOModes_DVD_DTS:          return m_eReplay == eReplayDVD && m_eDVDAudio == eDVDDTS;
  case cExtbSetup::eIOModes_DVD_PCM:          return m_eReplay == eReplayDVD && m_eDVDAudio == eDVDPCM;
  case cExtbSetup::eIOModes_DVD_MP2:          return m_eReplay == eReplayDVD && m_eDVDAudio == eDVDMP2;
  case cExtbSetup::eIOModes_DVD_4_3:          return m_eReplay == eReplayDVD && m_eDVDVideo == eDVDVideo4_3;
  case cExtbSetup::eIOModes_DVD_16_9:         return m_eReplay == eReplayDVD && m_eDVDVideo == eDVDVideo16_9;
  // MP3 Plugin Playbackmodes
  case cExtbSetup::eIOModes_MP3_Shuffle:      return m_eReplay == eReplayMP3 && (m_nMP3 & eMP3Shuffle);
  case cExtbSetup::eIOModes_MP3_Loop:         return m_eReplay == eReplayMP3 && (m_nMP3 & eMP3Loop);

  case cExtbSetup::eIOModes_Cutting:          return m_bCutting;
  // OSD
  case cExtbSetup::eIOModes_OsdActive:        return (m_nOsdMode & eOsdActive);
  case cExtbSetup::eIOModes_OsdInActive:      return (m_nOsdMode & eOsdActive) == 0;
  case cExtbSetup::eIOModes_OsdMessage:       return (m_nOsdMode & eOsdMessage);
  case cExtbSetup::eIOModes_OsdHelpKeyRed:    return (m_nOsdMode & eOsdHelpKeyRed);
  case cExtbSetup::eIOModes_OsdHelpKeyGreen:  return (m_nOsdMode & eOsdHelpKeyGreen);
  case cExtbSetup::eIOModes_OsdHelpKeyYellow: return (m_nOsdMode & eOsdHelpKeyYellow);
  case cExtbSetup::eIOModes_OsdHelpKeyBlue:   return (m_nOsdMode & eOsdHelpKeyBlue);
  
  default:
    return false;
  }
}

void cExtBoardIO::Update(bool bForce, unsigned int nMSDelay /* = 200 */ )
{
  unsigned int n,m;
  eLEDState eNewLEDState;
  
  //Update LED if changed 
  for(n=0;n<memberof(theSetup.m_ioLEDMode);++n) {
	
  	int nActiv = 0;
  	int nLEDOn = 0;
  	int nFlashing = 0;
  
    for(m=0;m<memberof(theSetup.m_ioLEDMode[0].nState);++m) {
	  if(theSetup.m_ioLEDMode[n].nState[m] != cExtbSetup::eIOModes_Unused) {
        ++nActiv;
        if(ShouldOn(theSetup.m_ioLEDMode[n].nState[m])) {
          nLEDOn = 1;  
          break;
        }
  	  }
    }

    for(m=0;m<memberof(theSetup.m_ioLEDMode[0].nFlash);++m) {
      if(theSetup.m_ioLEDMode[n].nFlash[m] != cExtbSetup::eIOModes_Unused) {
        ++nActiv;
        if(ShouldOn(theSetup.m_ioLEDMode[n].nFlash[m])){
          nFlashing = 1;  
          break;
        }
  	  }
    }

    if(nActiv) {
      if(theSetup.m_ioLEDMode[n].nReverse)
        nLEDOn = !nLEDOn;
      if(theSetup.m_ioLEDMode[n].nFlashReverse)
        nFlashing = !nFlashing;
      
      // Flashing or normal light, thats the question
      if(nFlashing || 
        (nFlashing && nLEDOn && theSetup.m_ioLEDMode[n].nFlashImportant))
        eNewLEDState = eLED_Flashing;
      else if(nLEDOn)
        eNewLEDState = eLED_On;
      else
        eNewLEDState = eLED_Off;
    
      
      if(bForce
        || m_eLastSendLEDState[n] != eNewLEDState) {
        
        switch(eNewLEDState) // Send new state to ext.board
        {
          case eLED_Flashing:  cLirc::send(m_szLED_FLASH[n],nMSDelay ,m_szLED_OFF[n]); break;
          case eLED_On:        cLirc::send(m_szLED_ON[n],   nMSDelay ,m_szLED_OFF[n]); break;
          case eLED_Off:       cLirc::send(m_szLED_OFF[n],  nMSDelay ,m_szLED_ON[n]);  break;
          case eLED_Unset:     break;
        }
        m_eLastSendLEDState[n] = eNewLEDState;
      }
    }
  }


  //Update Output if changed 
  for(n=0;n<memberof(theSetup.m_ioOutputMode);++n) {
	
  	int nActiv = 0;
  	int nOutputOn = 0;
  
    for(m=0;m<memberof(theSetup.m_ioOutputMode[0].nState);++m) {
	    if(theSetup.m_ioOutputMode[n].nState[m] != cExtbSetup::eIOModes_Unused) {
        ++nActiv;
        if(ShouldOn(theSetup.m_ioOutputMode[n].nState[m])){
          nOutputOn = 1;  
          break;
        }
      }
    }
    if(nActiv) {
      if(theSetup.m_ioOutputMode[n].nReverse)
        nOutputOn = !nOutputOn;
  
      if(bForce || m_nLastSendOutputState[n] != nOutputOn) {
        m_nLastSendOutputState[n] = nOutputOn;
        if(nOutputOn)
          cLirc::send(m_szOutput_ON[n],nMSDelay ,m_szOutput_OFF[n]);
        else
          cLirc::send(m_szOutput_OFF[n],nMSDelay ,m_szOutput_ON[n]);
      }
    }
  }
}


void cExtBoardIO::Recording(const cDevice *pDevice, const char *szName, const char *szFileName, bool bOn)
{
  unsigned int nCardIndex = pDevice->CardIndex();
  if (nCardIndex > memberof(m_nCardIsRecording) - 1 )
    nCardIndex = memberof(m_nCardIsRecording)-1;

  if (nCardIndex < memberof(m_nCardIsRecording)) {
    if (bOn) {
      ++m_nCardIsRecording[nCardIndex];
    }
    else {
      if (m_nCardIsRecording[nCardIndex] > 0)
        --m_nCardIsRecording[nCardIndex];
    }
  }
  else {
    esyslog("extb: Recording: only up to %d devices are supported by this plugin", memberof(m_nCardIsRecording));
  }
  
  Update(false);
}

void cExtBoardIO::Replaying(const cControl *pControl, const char *szName, const char *szFileName, bool bOn)
{
  if(bOn && szName) {
    m_eReplay = eReplayVDR;
    bool bFound = false;
    ///////////////////////////////////////////////////////////////////////
    //Looking for mp3-Plugin Replay : [LS] (444/666) title
    //
    if(strlen(szName)>6
      && *(szName+0)=='['
      && *(szName+3)==']'
      && *(szName+5)=='(')
    {
      unsigned int i;
      for(i=6;*(szName+i) != '\0';++i) { //search for [xx] (xxxx) title
        if(*(szName+i)==' ' && *(szName+i-1)==')') {
          bFound = true;
          break;
        }
      }
      if(bFound) //found MP3-Plugin replaymessage
      {
        m_eReplay = eReplayMP3;
        m_nMP3 = 0;
        m_nMP3 |= ((*(szName+1)) == 'L')?eMP3Loop:0;
        m_nMP3 |= ((*(szName+2)) == 'S')?eMP3Shuffle:0;
      }
    }
    ///////////////////////////////////////////////////////////////////////
    //Looking for DVD-Plugin Replay : 1/8 4/28,  de 2/5 ac3, no 0/7,  16:9, VOLUMENAME
    //cDvdPlayerControl::GerDisplayHeaderLine
    //                         titleinfo, audiolang,  spulang, aspect, title
    if(!bFound) {
      if(strlen(szName)>7) {
      unsigned int i,n;
      for(n=0,i=0;*(szName+i) != '\0';++i) {
      //search volumelabel after 4*", " => xxx, xxx, xxx, xxx, title
        if(*(szName+i)==' ' && *(szName+i-1)==',') {
          if(++n == 4) {
            bFound = true;
            break;
          }
        }
      }    
      if(bFound) //found DVD-Plugin replaymessage
      {
        m_eReplay = eReplayDVD;

        if((strstr(szName,"ac3") != NULL))
          m_eDVDAudio = eDVDAC3;
        else if((strstr(szName,"dts") != NULL))
          m_eDVDAudio = eDVDDTS;
        else if((strstr(szName,"pcm") != NULL))
          m_eDVDAudio = eDVDPCM;
        else if((strstr(szName,"mp2") != NULL))
          m_eDVDAudio = eDVDMP2;
        else
          m_eDVDAudio = eDVDNone;
        
        if((strstr(szName,"16:9") != NULL))
          m_eDVDVideo = eDVDVideo4_3;
        else if((strstr(szName,"4:3") != NULL))
          m_eDVDVideo = eDVDVideo16_9;
        else
          m_eDVDVideo = eDVDVideoNone;
      }
      }
    }
    if (!bFound) {
      m_eReplay = eReplayVDR;
    }
  }
  else
  {
    m_eReplay = eReplayNone;
  }
  Update(false);
}

void cExtBoardIO::Channel(int nChannelNumber)
{
  if(nChannelNumber > 0) {
    /*gcc3.3.4 only ??
    const cChannel * pChannel = Channels.GetByNumber(nChannelNumber);*/
    cChannel * pChannel = Channels.GetByNumber(nChannelNumber);
  
    if(pChannel && !pChannel->GroupSep()) {
      m_nChannel = 0; 
      m_nChannel  |= (0 != pChannel->Ca())?eHasCA:0;
#if VDRVERSNUM >= 10318
      m_nChannel  |= (0 != pChannel->Dpid(0))?eHasDpid:0;
      m_nChannel  |= (0 != pChannel->Apid(0))?eHasApid:0;
#else
      m_nChannel  |= (0 != pChannel->Dpid1())?eHasDpid:0;
      m_nChannel  |= (0 != pChannel->Apid2())?eHasApid:0;
#endif
      m_nChannel  |= (0 != pChannel->Vpid()) ?eHasVpid:eIsRadio;//if channel has no vpid, should be a radiostation
      m_nChannel  |= (0 != pChannel->Tpid()) ?eHasTpid:0;

      if(pChannel->IsSat()) {
        m_nChannel  |= eIsSourceSat;
        if (toupper(pChannel->Polarization()) == 'V')
          m_nChannel  |= eIsSatVPolar;
        if (toupper(pChannel->Polarization()) == 'H')
          m_nChannel  |= eIsSatHPolar;
      }  
      else if(pChannel->IsCable())
        m_nChannel  |= eIsSourceCable;
      else if(pChannel->IsTerr())
        m_nChannel  |= eIsSourceTerr;
/*      if (EITScanner.Active()) {
        m_nChannel  |= eIsEITScannerActive;
			}*/
    }
    else {
      m_nChannel = 0;
    }
  Update(false);
  }
}


//AUDIO/////////////////////////////////////////////////////////////////////////
const char * cExtBoardIO::m_szAnalog [2] = {
  "R_ANA",
  "S_ANA"
};

const char * cExtBoardIO::m_szDigital [2] = {
  "D_DIA",
  "S_DIA"
};

const char * cExtBoardIO::m_szAnalogAndDigital [2] = {
  "R_ANDIA",
  "S_ANDIA"
};


void cExtBoardIO::SendMute(unsigned int nMuteMode,bool bMute)
{
  const char* szSend = NULL;
  switch(nMuteMode)
  {
    case cExtbSetup::eMuteModes_Analog:       
        szSend = m_szAnalog[bMute?0:1];   
        break;
    case cExtbSetup::eMuteModes_Digital:      
        szSend = m_szDigital[bMute?0:1];
        break;
    case cExtbSetup::eMuteModes_AnalogDigital:
        szSend = m_szAnalogAndDigital[bMute?0:1];
        break;
    default: //eMuteModes_Unsed
        return;
  }
  if(szSend)
    cLirc::send(szSend);
}

bool cExtBoardIO::Volume(int nVolume, bool bAbsolute)
{
  int nAbsVolume;

#if APIVERSNUM >= 10402
  nAbsVolume = m_nLastVolume;
  if (bAbsolute) {
      nAbsVolume=100*nVolume/255;
  } else {
      nAbsVolume=nAbsVolume+100*nVolume/255;
  }
#else
  nAbsVolume = nVolume;
#endif

  bool bStateIsChanged = false;
  if(m_nLastVolume > 0 && 0 == nAbsVolume) {
    SendMute(theSetup.m_nMuteMode,true);
    bStateIsChanged = true;
  }
  else if(0 == m_nLastVolume && nAbsVolume > 0) {
    SendMute(theSetup.m_nMuteMode,false);
    bStateIsChanged = true;
  }
  m_nLastVolume = nAbsVolume;

  return bStateIsChanged;
}


void cExtBoardIO::StatusMessage(const char *szMessage)
{
    unsigned int n = m_nOsdMode & (~(eOsdMessage));
    bool bUpdate = false;
    if(szMessage) {
        
        struct 
        {
            bool& value;
            bool  state;
            const char* szMessage;    
        } messagetable[] = 
        {
            { m_bCutting,  true,      "Editing process started"  },
            { m_bCutting,  false,     "Editing process finished" },
            { m_bCutting,  false,     "Editing process failed!"  }
        };

        n |= eOsdMessage;
        
        unsigned int i;
        for(i = 0; i < (sizeof(messagetable)/sizeof(*messagetable));++i)
        {
            const char* szCmp = tr(messagetable[i].szMessage);
            if(0 == strncmp(szMessage,szCmp,strlen(szCmp)))    
            {
                messagetable[i].value = messagetable[i].state;
                bUpdate = true;
                break;
            }
        }
    }

    if(bUpdate || m_nOsdMode != n) 
    {
        m_nOsdMode = n;
        Update(false);
    }
}

void cExtBoardIO::OsdActive(bool bActive, unsigned int nMSDelay)
{
    unsigned int n = m_nOsdMode & (~(eOsdActive));
    n |= (bActive    ? eOsdActive : 0);
    if(m_nOsdMode != n) 
    {
        m_nOsdMode = n;
        Update(false,nMSDelay);
    }
}

void cExtBoardIO::OsdHelpKeys(bool bRed, bool bGreen, bool bYellow, bool bBlue, unsigned int nMSDelay)
{
    unsigned int n = m_nOsdMode & (~(eOsdHelpKeyRed|eOsdHelpKeyGreen|eOsdHelpKeyYellow|eOsdHelpKeyBlue));
    n |= (bRed    ? eOsdHelpKeyRed : 0);
    n |= (bGreen  ? eOsdHelpKeyGreen : 0);
    n |= (bYellow ? eOsdHelpKeyYellow : 0);
    n |= (bBlue   ? eOsdHelpKeyBlue : 0);

    if(m_nOsdMode != n) 
    {
        m_nOsdMode = n;
        Update(false,nMSDelay);
    }
}
