/*
 * 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"
#include "recdone.h"
#include "epgsearchcats.h"
#include <vdr/menu.h>

const char* ButtonBlue[3];
extern char menuSearchResultsTemplate[];
extern int menuSearchResultsTabs[];

static int CompareRecording(const void *p1, const void *p2)
{
  return (int)((*(cRecording **)p1)->start - (*(cRecording **)p2)->start);
}

// --- cMenuSearchResultsItem -------------------------------------------------------
cMenuSearchResultsItem::cMenuSearchResultsItem(const cEvent *EventInfo, bool EpisodeOnly, bool PreviewTimer)
{
    fileName = NULL;
    event = EventInfo;
    timerMatch = tmNone;
    episodeOnly = EpisodeOnly;
    previewTimer = PreviewTimer;
    Update(true);
}

bool cMenuSearchResultsItem::Update(bool Force)
{
    if (strlen(menuSearchResultsTemplate) == 0)
	return false;
    
    bool result = false;

    int OldTimerMatch = timerMatch;
    bool hasMatch = false; 
    cTimer* timer = NULL;
    if (event) timer = Timers.GetMatch(event, &timerMatch);
    if (timer) hasMatch = true;
    
    if (Force || timerMatch != OldTimerMatch) 
    {
	cChannel *channel = Channels.GetByChannelID(event->ChannelID(), true);
	char szChannelNr[6] = "";
	if (channel)
	    sprintf(szChannelNr, "%d", channel->Number());
	
	char t[2]="",v[2]="",r[2]="";
	char szStatus[4] = "";
	if (EPGSearchConfig.WarEagle)
	{
	    sprintf(t, "%c", event && hasMatch ? (timerMatch == tmFull) ? ((timer && timer->Recording())?249:253) : 't' : ' ');
	    sprintf(v, "%c", event && event->Vps() && (event->Vps() - event->StartTime()) ? 'V' : ' ');
	    sprintf(r, "%c", event && event->IsRunning() ? 251 : ' ');
	}
	else
	{
	    sprintf(t, "%c", event && hasMatch ? (timerMatch == tmFull) ? ((timer && timer->Recording())?'R':'T') : 't' : ' ');
	    sprintf(v, "%c", event && event->Vps() && (event->Vps() - event->StartTime()) ? 'V' : ' ');
	    sprintf(r, "%c", event && event->IsRunning() ? '*' : ' ');
	}
	if (t[0] != 'T' && previewTimer)
	    t[0] = 'P';
	
	sprintf(szStatus, "%s%s%s", t,v,r);
	
	char day[3] = "";
	char date[9] = "";
	char dateshort[7] = "";
	if (event)
	{
	    struct tm tm_r;
	    const time_t t = event->StartTime();
	    tm *tm = localtime_r(&t, &tm_r);
	    strftime(day, sizeof(day), "%d", tm);
	    strftime(date, sizeof(date), "%d.%m.%y", tm);
	    strftime(dateshort, sizeof(dateshort), "%d.%m.", tm);
 	}
	
	char* buffer = strdup(menuSearchResultsTemplate);
	char* tmp = strreplaceall(buffer, '|', "\t");
	free(buffer);
	buffer = tmp;

	if (!strcasestr(buffer, "%subtitle%")) // make sure, there is a subtitle
	    buffer = strreplacei(buffer, "%title%", "%title% ~ %subtitle%");
	if (episodeOnly)
	    buffer = strreplacei(buffer, "%title%", "");
	    
	while(strcasestr(buffer, "%colon%"))
	    buffer = strreplacei(buffer, "%colon%", ":");
	buffer = strreplacei(buffer, "%time%", event?GETTIMESTRING(event):"");
	buffer = strreplacei(buffer, "%time_w%", event?WEEKDAYNAME(event->StartTime()):"");
	buffer = strreplacei(buffer, "%time_d%", event?day:"");
	buffer = strreplacei(buffer, "%date%", event?date:"");
	buffer = strreplacei(buffer, "%datesh%", event?dateshort:"");
	buffer = strreplacei(buffer, "%title%", event?event->Title():tr(">>> no info! <<<"));
	buffer = strreplacei(buffer, "%subtitle%", (event && event->ShortText())?event->ShortText():"");
	buffer = strreplacei(buffer, "%status%", szStatus);
	buffer = strreplacei(buffer, "%t_status%", t);
	buffer = strreplacei(buffer, "%v_status%", v);
	buffer = strreplacei(buffer, "%r_status%", r);
	if (SearchExtCats.Count() > 0)
	{
	    cSearchExtCat *SearchExtCat = SearchExtCats.First();
	    while (SearchExtCat) 
	    {
		char* catvalue = NULL;
		if (event)
		    catvalue = GetExtEPGValue(event->Description(), SearchExtCat->name);
		char* catname = NULL;
		asprintf(&catname, "%%%s%%", SearchExtCat->name);
		buffer = strreplacei(buffer, catname, catvalue?catvalue:"");
		if (catvalue) free(catvalue);
		free(catname);
		SearchExtCat = SearchExtCats.Next(SearchExtCat);
	    }
	}
	
	if (channel)
	{
	    buffer = strreplacei(buffer, "%chnr%", szChannelNr);
#if VDRVERSNUM >= 10315    
	    buffer = strreplacei(buffer, "%chsh%", channel?channel->ShortName(true):"");
#endif
	    buffer = strreplacei(buffer, "%chlng%", channel?channel->Name():"");
	}
	
	buffer = FixSeparators(buffer, '~');
	buffer = FixSeparators(buffer, ':');
	buffer = FixSeparators(buffer, '-');
	
	SetText(buffer, false);
	return true;
    }
    return result;
}

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"), GetTab(1), GetTab(2), GetTab(3), GetTab(4), GetTab(5))
{
    ButtonBlue[0] = strdup(tr("Button$all channels"));
    ButtonBlue[1] = strdup(tr("Button$only FTA"));
    ButtonBlue[2] = strdup(tr("Button$Timer preview"));

    // create a dummy search
    asprintf(&m_pSearchString, "%s", p2);
    searchExt = new cSearchExt;
    strcpy(searchExt->search, m_pSearchString);
    searchExt->mode = 0; // substring
    searchExt->useTitle = 1;
    searchExt->useSubtitle = 0;
    searchExt->useDescription = 0;

    blacklist = NULL;
    m_bSort = true;
    m_bSearchInRecordings = bSearchInRecordings;
    modeYellow = showTitleEpisode;
    modeBlue = EPGSearchConfig.ignorePayTV?showNoPayTV:showAll;
    
    if (bSearchInRecordings)
	ScanRecordings();
    else
	BuildList(true);
}

cMenuSearchResults::~cMenuSearchResults()
{
    if (m_pSearchString)
    {
	delete searchExt;
	free(m_pSearchString);
    }
}

cMenuSearchResults::cMenuSearchResults(cSearchExt* SearchExt)
    :cOsdMenu(tr("Search results"), GetTab(1), GetTab(2), GetTab(3), GetTab(4), GetTab(5))
{
    ButtonBlue[0] = strdup(tr("Button$all channels"));
    ButtonBlue[1] = strdup(tr("Button$only FTA"));
    ButtonBlue[2] = strdup(tr("Button$Timer preview"));

    searchExt = SearchExt;
    blacklist = NULL;
    m_pSearchString = NULL;
    m_bSort = true;
    m_bSearchInRecordings = false;
    modeYellow = showTitleEpisode;
    modeBlue = searchExt->useChannel==3?showNoPayTV:(EPGSearchConfig.ignorePayTV?showNoPayTV:showAll);
    
    BuildList();
}

cMenuSearchResults::cMenuSearchResults(cBlacklist* Blacklist)
    :cOsdMenu(tr("Blacklist results"), GetTab(1), GetTab(2), GetTab(3), GetTab(4), GetTab(5))
{
    ButtonBlue[0] = strdup(tr("Button$all channels"));
    ButtonBlue[1] = strdup(tr("Button$only FTA"));
    ButtonBlue[2] = strdup(tr("Button$Timer preview"));

    blacklist = Blacklist;
    searchExt = NULL;
    m_pSearchString = NULL;
    m_bSort = true;
    m_bSearchInRecordings = false;
    modeYellow = showTitleEpisode;
    modeBlue = blacklist->useChannel==3?showNoPayTV:(EPGSearchConfig.ignorePayTV?showNoPayTV:showAll);
    
    BuildBlacklist();
}

int cMenuSearchResults::GetTab(int Tab)
{
    if (strlen(menuSearchResultsTemplate) > 0)
	return menuSearchResultsTabs[Tab-1];

    if (Tab==1)
	return EPGSearchConfig.showChannelNr?CHNUMWIDTH:12;
    if (Tab==2)
	return EPGSearchConfig.showChannelNr?12:7;
    if (Tab==3)
	return EPGSearchConfig.showChannelNr?7:6;
    if (Tab==4)
	return EPGSearchConfig.showChannelNr?6:3;
    if (Tab==5)
	return EPGSearchConfig.showChannelNr?3:30; 
    return 0;
}

bool cMenuSearchResults::BuildList(bool IgnoreRunning)
{
    if (searchExt) return BuildSearchlist(IgnoreRunning);
    if (blacklist) return BuildBlacklist(IgnoreRunning);
    return false;
}

bool cMenuSearchResults::BuildSearchlist(bool IgnoreRunning)
{
    time_t now = time(NULL);
    cSearchResults* pSearchResults = searchExt->Run(modeBlue == showNoPayTV);
    Clear();
    if (pSearchResults)
    {
	pSearchResults->SortBy(m_bSort? CompareEventTime: CompareEventChannel);
	
	if (modeBlue == showTimerPreview && searchExt->avoidRepeats)
	    searchExt->CheckRepeatTimers(pSearchResults);
	
	for (cSearchResult* pResultObj = pSearchResults->First(); 
	     pResultObj; 
	     pResultObj = pSearchResults->Next(pResultObj))
	{
	    if (IgnoreRunning && now > pResultObj->event->StartTime())
		continue;
	    if (!(searchExt->useAsSearchTimer && searchExt->avoidRepeats && modeBlue == showTimerPreview))
		pResultObj->needsTimer = false;
	    
	    Add(new cMenuSearchResultsItem(pResultObj->event, modeYellow == showEpisode, pResultObj->needsTimer));
	}
	
	delete pSearchResults;
    }
    SetHelpKeys();  
    char* szTitle = NULL;
    asprintf(&szTitle, "%d %s - %s", Count(), tr("Search results"), searchExt->search);
    SetTitle(szTitle);
    free(szTitle);
    Display();
    
    return true;
}

bool cMenuSearchResults::BuildBlacklist(bool IgnoreRunning)
{
    time_t now = time(NULL);
    cSearchResults* pSearchResults = blacklist->Run();
    Clear();
    if (pSearchResults)
    {
	pSearchResults->SortBy(m_bSort? CompareEventTime: CompareEventChannel);
	
	for (cSearchResult* pResultObj = pSearchResults->First(); 
	     pResultObj; 
	     pResultObj = pSearchResults->Next(pResultObj))
	{
	    if (IgnoreRunning && now > pResultObj->event->StartTime())
		continue;
	    Add(new cMenuSearchResultsItem(pResultObj->event, modeYellow == showEpisode, false));
	}
	
	delete pSearchResults;
    }
    SetHelpKeys();  
    char* szTitle = NULL;
    asprintf(&szTitle, "%d %s - %s", Count(), tr("Blacklist results"), blacklist->search);
    SetTitle(szTitle);
    free(szTitle);
    Display();
    
    return true;
}

bool cMenuSearchResults::Update(void)
{
  bool result = false;
  for (cOsdItem *item = First(); item; item = Next(item)) {
      if (((cMenuSearchResultsItem *)item)->Update())
         result = true;
      }
  return result;
}

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 *), CompareRecording);

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

    delete pArray;

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

void cMenuSearchResults::SetHelpKeys()
{
    cMenuSearchResultsItem *item = (cMenuSearchResultsItem *)Get(Current());
    bool hasTimer = item && item->Selectable() && item->timerMatch == tmFull;

    ModeBlueSR nextModeBlue = (ModeBlueSR)(((int)modeBlue+1)%3);
    if (nextModeBlue == showTimerPreview && 
	(!searchExt || (searchExt && (searchExt->useAsSearchTimer == 0 || searchExt->avoidRepeats == 0))))
	nextModeBlue = (ModeBlueSR)(((int)nextModeBlue+1)%3);

    if (toggleKeys==0)
	SetHelp((EPGSearchConfig.redkeymode==0?(hasTimer?tr("Button$Timer"):tr("Button$Record")):tr("Button$Commands")), m_bSort? tr("Button$by channel"):tr("Button$by time"), modeYellow==showTitleEpisode?tr("Button$Episode"):tr("Button$Title"), blacklist?NULL:ButtonBlue[(int)nextModeBlue]);
    else
	SetHelp((EPGSearchConfig.redkeymode==1?(hasTimer?tr("Button$Timer"):tr("Button$Record")):tr("Button$Commands")), m_bSort? tr("Button$by channel"):tr("Button$by time"), modeYellow==showTitleEpisode?tr("Button$Episode"):tr("Button$Title"), blacklist?NULL:ButtonBlue[(int)nextModeBlue]);
}

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) {
      if (item->timerMatch == tmFull) 
      {
	  int tm = tmNone;
	  cTimer *timer = Timers.GetMatch(item->event, &tm);
	  if (timer)
#if VDRVERSNUM && VDRVERSNUM >= 10331
	      if (EPGSearchConfig.useVDRTimerEditMenu)
		  return AddSubMenu(new cMenuEditTimer(timer));
	      else
#endif
		  return AddSubMenu(new cMenuMyEditTimer(timer, false, item->event));
      }

     cTimer *timer = new cTimer(item->event);
     PrepareTimerFile(item->event, timer);
     cTimer *t = Timers.GetTimer(timer);
     if (EPGSearchConfig.onePressTimerCreation == 0 || t || !item->event || (!t && item->event && item->event->StartTime() - (Setup.MarginStart+2) * 60 < time(NULL))) 
     {
	 if (t)
	 {
	     delete timer;
	     timer = t;      
	 }
#if VDRVERSNUM && VDRVERSNUM >= 10331
	 if (EPGSearchConfig.useVDRTimerEditMenu)
	     return AddSubMenu(new cMenuEditTimer(timer, !t));
	 else
#endif
	     return AddSubMenu(new cMenuMyEditTimer(timer, !t, item->event));     
     }
     else
     {
	 Timers.Add(timer);
	 timer->Matches();

#if VDRVERSNUM && VDRVERSNUM >= 10315
	 Timers.SetModified();
#endif

#if VDRVERSNUM && VDRVERSNUM >= 10323
	 isyslog("timer %s added (active)", *timer->ToDescr());
#else
	 isyslog("timer %d added (active)", timer->Index()+1);
#endif

	 if (HasSubMenu())
	     CloseSubMenu();
	 if (Update())
	     Display();
	 SetHelpKeys();
     }
  }
  return osContinue;
}


eOSState cMenuSearchResults::Switch(void)
{
  cMenuSearchResultsItem *item = (cMenuSearchResultsItem *)Get(Current());
  if (item) {
     cChannel *channel = Channels.GetByChannelID(item->event->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->event) {
        cMenuSearchCommands *menu;
        eOSState state = AddSubMenu(menu = new cMenuSearchCommands(tr("EPG Commands"), mi->event, true, searchExt));
        if (Key != kNone)
           state = menu->ProcessKey(Key);
        return state;
     }
  return osContinue;
}

eOSState cMenuSearchResults::ShowSummary()
{
    if (Count())
    {
	const cEvent *ei = ((cMenuSearchResultsItem *)Get(Current()))->event;
	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)
{
    bool HadSubMenu = HasSubMenu();
    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->event) {
				    return AddSubMenu(new cMenuSearchCommands(tr("EPG Commands"),mi->event, false, searchExt));
				}
			    }
			}
		    }
		    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
		    {
			if (!blacklist)
			{
			    modeBlue = (ModeBlueSR)(((int)modeBlue+1)%3);
			    if (modeBlue == showTimerPreview && 
				(!searchExt || (searchExt && (searchExt->useAsSearchTimer == 0 || searchExt->avoidRepeats == 0))))
				modeBlue = (ModeBlueSR)(((int)modeBlue+1)%3);
			    
			    if (modeBlue == showTimerPreview)
				m_bSort = true; // show always sorted by channel
			    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;
	}
    }
    if (!HasSubMenu()) 
    {
	if ((HadSubMenu || gl_TimerProgged) && Update())
	{
	    if (gl_TimerProgged) // when using epgsearch's timer edit menu, update is delayed because of SVDRP
	    {
		gl_TimerProgged = 0;
		SetHelpKeys();
	    }
	    Display();
	}
	if (Key != kNone)
	    SetHelpKeys();
    }
    return state;
}


