/*
 * menu_searchresults.c: A plugin for the Video Disk Recorder
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id$
 */


#include "menu_searchresults.h"
#include "menu_myedittimer.h"
#include "menu_event.h"
#include "menu_commands.h"
#include "epgsearchcfg.h"
#include "tools.h"
#include "changrp.h"

static int CompareEventTime(const void *p1, const void *p2)
{
  return (int)((*(cEvent **)p1)->StartTime() - (*(cEvent **)p2)->StartTime());
}

static int CompareEventChannel(const void *p1, const void *p2)
{
  return (int)( Channels.GetByChannelID((*(const cEvent **)p1)->ChannelID())->Number() - Channels.GetByChannelID((*(const cEvent **)p2)->ChannelID())->Number());
}




// --- cMenuSearchResultsItem -------------------------------------------------------
cMenuSearchResultsItem::cMenuSearchResultsItem(const cEvent *EventInfo, bool bEpisodeOnly)
{
    fileName = NULL;
    eventInfo = EventInfo;
    char *buffer = NULL;
    cChannel *channel = Channels.GetByChannelID(eventInfo->ChannelID(), true);
    int TimerMatch;
    char t = Timers.GetMatch(EventInfo, &TimerMatch) ? (TimerMatch == tmFull) ? 'T' : 't' : ' ';
    char v = eventInfo->Vps() && (eventInfo->Vps() - eventInfo->StartTime()) ? 'V' : ' ';
    char r = eventInfo->IsRunning() ? '*' : ' ';

    if(eventInfo->ShortText())
    {
	if (!bEpisodeOnly)
	    asprintf(&buffer, "%.*s\t%.*s\t%.*s\t%c%c%c %s~%s", 10, CHANNELNAME(channel), 6, GETDATESTRING(eventInfo), 5, GETTIMESTRING(eventInfo), t, v, r, bEpisodeOnly?NULL:eventInfo->Title(), eventInfo->ShortText());
	else
	    asprintf(&buffer, "%.*s\t%.*s\t%.*s\t%c%c%c %s", 10, CHANNELNAME(channel), 6, GETDATESTRING(eventInfo), 5, GETTIMESTRING(eventInfo), t, v, r, eventInfo->ShortText());
    }
    else
	asprintf(&buffer, "%.*s\t%.*s\t%.*s\t%c%c%c %s", 10, CHANNELNAME(channel), 6, GETDATESTRING(eventInfo), 5, GETTIMESTRING(eventInfo), t, v, r, eventInfo->Title());
    SetText(buffer, false);
}

cMenuSearchResultsItem::cMenuSearchResultsItem(cRecording *Recording)
{
  fileName = strdup(Recording->FileName());
  SetText(Recording->Title('\t'));
}

// --- cMenuSearchResults -------------------------------------------------------

const cEvent *cMenuSearchResults::scheduleEventInfo = NULL;
int cMenuSearchResults::currentChannel = 0;

cMenuSearchResults::cMenuSearchResults(const char *p2, bool bSearchInRecordings)
:cOsdMenu(bSearchInRecordings ? tr("found recordings") : tr("additional starting times"), 12, 7, 6)
{
  asprintf(&m_pSearchString, "%s", p2);
  searchExt = NULL;
  m_bSort = true;
  m_bSearchInRecordings = bSearchInRecordings;
  modeYellow = showTitleEpisode;
  m_bWithPayTV = (EPGSearchConfig.ignorePayTV != 1);

  if (bSearchInRecordings)
     ScanRecordings();
  else
      BuildList();
}

cMenuSearchResults::cMenuSearchResults(cSearchExt* SearchExt)
:cOsdMenu(tr("Search results"), 12, 7, 6)
{
  searchExt = SearchExt;
  m_pSearchString = NULL;
  m_bSort = true;
  m_bSearchInRecordings = false;
  modeYellow = showTitleEpisode;
  m_bWithPayTV = (EPGSearchConfig.ignorePayTV != 1);

  BuildList();
}

cEvent * cMenuSearchResults::GetEventByTitle(const cSchedule *schedules, const char *p2, const cEvent *Start) 
{
  if(!p2) return NULL;

  cEvent *pe = NULL;
  cEvent *p1=schedules->Events()->First();
// first we have to search for the Start item
  if(Start) {
     cEvent *p;
     for (p = schedules->Events()->First(); p && p!=Start; p = schedules->Events()->Next(p));
     if(p)
        p1=schedules->Events()->Next(p);
     }

  time_t tNow=time(NULL);
  for (cEvent *p = p1; p; p = schedules->Events()->Next(p)) {
     if(!p) {
        break;
     }
     if (tNow < p->EndTime()) {
        if(p->Title() && !strcasecmp(p2, p->Title())) {
           pe=p;
           break;
        }
     }
  }
  return pe;
}


bool cMenuSearchResults::BuildList(void)
{
  cSchedulesLock schedulesLock;
  const cSchedules *schedules;
  schedules = cSchedules::Schedules(schedulesLock);
  if(!schedules) {
     esyslog("cMenuSearchResults::BuildList scheduler is empty!");
     return false;
  }
  const cSchedule *Schedule = schedules->First();
  const cEvent **pArray = NULL;
  const cEvent *pPrevEvent = NULL;
  int num = 0;
  Clear();
  while (Schedule) {
        if (searchExt && searchExt->useChannel == 1 && searchExt->channelMin && searchExt->channelMax)
	{
	    cChannel* channel = Channels.GetByChannelID(Schedule->ChannelID(),true);
	    if (channel && (searchExt->channelMin->Number() > channel->Number() || searchExt->channelMax->Number() < channel->Number()))
	    {
		Schedule = (const cSchedule *)schedules->Next(Schedule);
		continue;
	    }
	}
        if (searchExt && searchExt->useChannel == 2 && searchExt->channelGroup)
	{
	    cChannel* channel = Channels.GetByChannelID(Schedule->ChannelID(),true);
	    cChannelGroup* group = ChannelGroups.GetGroupByName(searchExt->channelGroup);
	    if (!group || !group->ChannelInGroup(channel))
	    {
		Schedule = (const cSchedule *)schedules->Next(Schedule);
		continue;
	    }
	}

	if (!m_bWithPayTV)
	{
	    cChannel* channel = Channels.GetByChannelID(Schedule->ChannelID(),true);
	    if (channel && channel->Ca() > CACONFBASE)
	    {
		Schedule = (const cSchedule *)schedules->Next(Schedule);
		continue;
	    }
	}

        pArray = (const cEvent **)realloc(pArray, (num + 1) * sizeof(cEvent *));
        pPrevEvent = NULL;
        do {
	   if (!searchExt)
           	pArray[num] = GetEventByTitle(Schedule, m_pSearchString, pPrevEvent);
	   else
           	pArray[num] = searchExt->GetEventBySearchExt(Schedule, pPrevEvent);
           pPrevEvent = pArray[num];
           if (pArray[num]) {
              cChannel *channel = Channels.GetByChannelID(pArray[num]->ChannelID(), true);
              if (channel) {
                 num++;
                 pArray = (const cEvent **)realloc(pArray, (num + 1) * sizeof(cEvent *));
              }
           }
        } while(pPrevEvent);
        Schedule = (const cSchedule *)schedules->Next(Schedule);
  }
  qsort(pArray, num, sizeof(cEvent *), m_bSort? CompareEventTime: CompareEventChannel);

  for (int a = 0; a < num; a++)
     Add(new cMenuSearchResultsItem(pArray[a], modeYellow == showEpisode));

  delete pArray;

  SetHelpKeys();

  if (searchExt)
  {
      char* szTitle = NULL;
      asprintf(&szTitle, "%d %s - %s", Count(), tr("Search results"), searchExt->search);
      SetTitle(szTitle);
      free(szTitle);
  }

  Display();

  return true;
}

bool cMenuSearchResults::ScanRecordings(void)
{
    cRecording **pArray = NULL;
    int num = 0;
    
    Clear();
#if VDRVERSNUM <= 10310    
    cRecordings Recordings;
    bool recordings = Recordings.Load();
    if (!recordings)
	return true;
#endif

   for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording)) {
	if (strstr(recording->Title(), m_pSearchString) ||
	    strstr(m_pSearchString, recording->Title())) {
	    pArray = (cRecording **)realloc(pArray, (num + 1) * sizeof(cRecording *));
	    pArray[num++] = recording;
	}
    }

    qsort(pArray, num, sizeof(cRecording *), m_bSort? CompareEventTime: CompareEventChannel);

    for (int a = 0; a < num; a++)
	Add(new cMenuSearchResultsItem(pArray[a]));

    delete pArray;

    SetHelp(NULL);
    Display();
    return true;
}

void cMenuSearchResults::SetHelpKeys()
{
    if (toggleKeys==0)
	SetHelp((EPGSearchConfig.redkeymode==0?tr("Record"):tr("Button$Commands")), m_bSort? tr("by channel"):tr("by time"), modeYellow==showTitleEpisode?tr("Episode"):tr("Title"), m_bWithPayTV?tr("only FTA"):tr("all channels"));
    else
	SetHelp((EPGSearchConfig.redkeymode==1?tr("Record"):tr("Button$Commands")), m_bSort? tr("by channel"):tr("by time"), modeYellow==showTitleEpisode?tr("Episode"):tr("Title"), m_bWithPayTV?tr("only FTA"):tr("all channels"));
}

cRecording *cMenuSearchResults::GetRecording(cMenuSearchResultsItem *Item)
{
#if VDRVERSNUM <= 10310    
    cRecordings Recordings;
    bool recordings = Recordings.Load();
    if (!recordings)
	return NULL;
#endif

  cRecording *recording = Recordings.GetByName(Item->FileName());
  if (!recording)
     Skins.Message(mtError, tr("Error while accessing recording!"));
  return recording;
}
eOSState cMenuSearchResults::Play(void)
{
  cMenuSearchResultsItem *ri = (cMenuSearchResultsItem*)Get(Current());
  if (ri) 
  {
      cRecording *recording = GetRecording(ri);
      if (recording) {
	  cReplayControl::SetRecording(recording->FileName(), recording->Title());
	  return osReplay;
      }
  }

  return osContinue;
}

eOSState cMenuSearchResults::Record(void)
{
  cMenuSearchResultsItem *item = (cMenuSearchResultsItem *)Get(Current());
  if (item) {
     cTimer *timer = new cTimer(item->eventInfo);
     cTimer *t = Timers.GetTimer(timer);
     if (t) {
        delete timer;
        timer = t;
        }
     return AddSubMenu(new cMenuMyEditTimer(timer, !t, item->eventInfo));
     }
  return osContinue;
}


eOSState cMenuSearchResults::Switch(void)
{
  cMenuSearchResultsItem *item = (cMenuSearchResultsItem *)Get(Current());
  if (item) {
     cChannel *channel = Channels.GetByChannelID(item->eventInfo->ChannelID(), true);
     if (channel && cDevice::PrimaryDevice()->SwitchChannel(channel, true))
        return osEnd;
     }
  Skins.Message(mtInfo, tr("Can't switch channel!")); 
  return osContinue;
}

eOSState cMenuSearchResults::Commands(eKeys Key)
{
  if (HasSubMenu() || Count() == 0)
     return osContinue;
  cMenuSearchResultsItem *mi = (cMenuSearchResultsItem *)Get(Current());
  if (mi && mi->eventInfo) {
        cMenuSearchCommands *menu;
        eOSState state = AddSubMenu(menu = new cMenuSearchCommands(tr("EPG Commands"), mi->eventInfo, true));
        if (Key != kNone)
           state = menu->ProcessKey(Key);
        return state;
     }
  return osContinue;
}

eOSState cMenuSearchResults::ShowSummary()
{
    if (Count())
    {
	const cEvent *ei = ((cMenuSearchResultsItem *)Get(Current()))->eventInfo;
	if (ei) 
	{
	    cChannel *channel = Channels.GetByChannelID(ei->ChannelID(), true);
	    if (channel)
		return AddSubMenu(new cMenuEventSearch(ei, channel->Number() != cDevice::CurrentChannel()));
	}
    }
    return osContinue;
}

eOSState cMenuSearchResults::ProcessKey(eKeys Key)
{
  eOSState state = cOsdMenu::ProcessKey(Key);

  if (state == osUnknown) {
      if (!m_bSearchInRecordings)
      {
	  switch (Key) {
	      case kFastRew:
		  if(HasSubMenu()) 	     
		  {
		      CursorUp();
		      return ShowSummary();
		  }
		  break;
	      case kFastFwd:
		  if(HasSubMenu()) 	     
		  {
		      CursorDown();
		      return ShowSummary();
		  }
		  break;
	      case kRecord:
	      case kRed:
		  if(HasSubMenu()) {
		      state = Record();
		      break;
		  } 
		  if (Count())
		  {
		      if (EPGSearchConfig.redkeymode==toggleKeys)
			  state = Record();
		      else
		      {
			  cMenuSearchResultsItem* mi = (cMenuSearchResultsItem*)Get(Current());
			  if (mi) {
			      if (mi->eventInfo) {
				  return AddSubMenu(new cMenuSearchCommands(tr("EPG Commands"),mi->eventInfo));
			      }
			  }
		      }
		  }
		  break;
	      case k0:
		  if(!HasSubMenu()) 
		  {
		      toggleKeys = 1 - toggleKeys;
		      SetHelpKeys();
		  }
		  state = osContinue;
		  break;
	      case k1...k9: 
		  if (!HasSubMenu())
		      return Commands(Key);
		  else
		      state = osContinue;
		  break;
	      case kGreen:  {
		  if(HasSubMenu()) 	     
		  {
		      CursorUp();
		      return ShowSummary();
		  }
		  else
		  {
		      m_bSort=!m_bSort;
		      BuildList();
		      state = osContinue;
		  }
	      }
		  break;
	      case kBlue:
		  if (HasSubMenu())
		      return Switch();
		  else
		  {
		      m_bWithPayTV = !m_bWithPayTV;
		      BuildList();
		      state = osContinue;
		  }
		  break;
	      case kYellow: 
		  if(HasSubMenu()) 	     
		  {
		      CursorDown();
		      return ShowSummary();
		  }
		  else
		  {
		      modeYellow = (modeYellow==showTitleEpisode?showEpisode:showTitleEpisode);
		      BuildList();
		      state = osContinue;
		  }
		  break;
	      case kOk:
		  if(HasSubMenu()) 
		  {
		      state = cOsdMenu::ProcessKey(Key);
		      break;
		  }
		  if (Count())
		      return ShowSummary();
		  else
		      state = osBack;
		  break;
	      default:      
		  break;
	  }
      }
      else
      {
	  if (Key == kOk)
	      return Play();
	  else
	      state = osContinue;
      }
  }
  return state;
}


