
#include <math.h>   // isdigit
#include <ctype.h>  // roundf

#include <vdr/device.h>
#include <vdr/epg.h>
#include <vdr/skins.h>
#include <vdr/tools.h>
#include <vdr/menu.h>

#include "menudirector.h"
#include "directorosd.h"

static char TimerMatchChars[3] = { ' ', 253, 249 };    // WarEagle

static const char* const ProgressBar[7] =
{
  "[      ]",
  "[|     ]",
  "[||    ]",
  "[|||   ]",
  "[||||  ]",
  "[||||| ]",
  "[||||||]"
};

cMenuDirectorItem::cMenuDirectorItem(cMenuDirector* menu, const cChannel* channel, 
                                     const cEvent* event, int details, int next)
{
  mChannel = channel;
  mEvent = event;
  mDetails = details;
  mNext = next;
  mMenu = menu;
  lastTimerMatch = tmNone;

  Update(true);
}

int cMenuDirectorItem::Update(int force)
{
   int match = tmNone;
   int changed = false;
   char* text = 0;
   char* chName = 0;
   const char* p = mChannel->ShortName(true);

   if (mEvent)
      Timers.GetMatch(mEvent, &match);

   if (force || lastTimerMatch != match)
   {
      if (!mNext)
      {
         if (strlen(p) >= 3 && strstr(p, "D ") == p && isdigit(p[2]))
            asprintf(&chName, "%s%.*s", mMenu->hotKey(""), 3, p);
         else
            asprintf(&chName, "%s%s", mMenu->hotKey(""), p);
      }
      
      if (mDetails && mEvent)
      {
         char r = mEvent->SeenWithin(30) && mEvent->IsRunning() ? 251 : ' ';  // WarEagle
         char t = TimerMatchChars[match];
         int p = (int)roundf((float)(time(0) - mEvent->StartTime()) 
                                    / (float)(mEvent->Duration()) * 6.0);

         if      (p < 0)  p = 0;
         else if (p > 6)  p = 6;

         asprintf(&text, "%.*s\t%c%c \t%s\t  %s - %s",
                  17, chName ? chName : "",
                  t, r,
                  mNext ? "" : ProgressBar[p],
                  *mEvent->GetTimeString(),
                  *mEvent->GetEndTimeString());
      }

      if (!text)
         asprintf(&text, "%s", chName ? chName : "");

      SetText(text);
      changed = true;

      if (text)   free(text);
      if (chName) free(chName);
   }

   return changed;
}

const cChannel* cMenuDirector::mReferenceChannel = NULL;
cMenuDirectorData* cMenuDirector::mData = NULL;

cMenuDirector::cMenuDirector(const char* title, cMenuDirectorData* data)
#if VDRVERSNUM >= 10326
   : cOsdMenu(title)
#else
   : cOsdMenu(title,3)
#endif
{
  mData = data;
  mCurrentChannel = NULL;

  DetermineReferenceChannel();
  Update();

  Display();
  SetHelpKeys(0);
}

cMenuDirector::~cMenuDirector()
{
}

cOsdMenu* cMenuDirector::Create(const char* title, cOsdProxyData* data)
{
  return new cMenuDirector(title, (cMenuDirectorData*)data);
}

void cMenuDirector::Release()
{
  mReferenceChannel = NULL;
  mData = NULL;
}

void cMenuDirector::ChannelSwitchNonOsd(const cChannel* channel)
{
  cDevice::PrimaryDevice()->SwitchChannel(channel, true);
  if ((mData) && (mData->mDisplayChannelInfo == 1))
    Skins.Message(mtInfo,channel->Name());
}

void cMenuDirector::ChannelSwitchNonOsd(int direction)
{
  if (!mReferenceChannel)
    return;

  if ((mData) && (mData->mSwapKeys == 1))
    direction *= -1;

  if(GetLinkChannelsOutOfChannel(mReferenceChannel))
  {
    cChannel* currentChannel = Channels.GetByNumber(cDevice::PrimaryDevice()->CurrentChannel());
    const cLinkChannels* linkChannels = GetLinkChannelsOutOfChannel(mReferenceChannel);

    if ((currentChannel == mReferenceChannel) && (direction == -1))
    {
      ChannelSwitchNonOsd(linkChannels->Last()->Channel());
      return;
    }
    if ((currentChannel == mReferenceChannel) && (direction == +1))
    {
      ChannelSwitchNonOsd(linkChannels->First()->Channel());
      return;
    }
    
    const cChannel* prevChannel = mReferenceChannel;

    for (cLinkChannel* linkChannel = linkChannels->First(); linkChannel; 
         linkChannel = linkChannels->Next(linkChannel))
    {
      if (linkChannel->Channel() == currentChannel)
      {
        if (direction == -1)
        {
          ChannelSwitchNonOsd(prevChannel);
          return;
        }
        else
        {
          if (linkChannels->Next(linkChannel) == NULL)
            ChannelSwitchNonOsd(mReferenceChannel);
          else
            ChannelSwitchNonOsd(linkChannels->Next(linkChannel)->Channel());
          return;
        }
      }

      prevChannel = linkChannel->Channel();
    }
  }
}

eOSState cMenuDirector::KeyHandlerFunction(eKeys Key)
{
   switch (Key)
   {
      case kUp:     ChannelSwitchNonOsd(-1);  break;
      case kDown:   ChannelSwitchNonOsd(+1);  break;

      default: break;
   }

  return osContinue;
}

eOSState cMenuDirector::ProcessKey(eKeys Key)
{
   int switched = false;

   if      ((mData->mSwapKeysInMenu) && (Key == kUp))    Key = kDown;
   else if ((mData->mSwapKeysInMenu) && (Key == kDown))  Key = kUp;
  
   cMenuDirectorItem* lastItem = (cMenuDirectorItem*)Get(Current());
   eOSState state = cOsdMenu::ProcessKey(Key);
   cMenuDirectorItem* item = (cMenuDirectorItem*)Get(Current());

   if (item && item != lastItem)
      SetHelpKeys(item);

   if (Key == kDown || Key == kUp || Key == kLeft || Key == kRight)
      state = osUnknown;

   if (state == osUnknown) 
   {
      switch (Key) 
      {
         case kBlue:   
         {     
            if (item && item->Event())
               return AddSubMenu(new cMenuEvent(item->Event()));

            break;
         }
         case kRed:
         {     
            if (item && item->Event())
               Record(item);

            break;
         }
         case kYellow:
         {
            mData->mShowOldDirectorOsd = 1;
            cRemote::CallPlugin("arghdirector");
            return osEnd;
         }
         case kOk:
         {
            if (item && item->Channel() && item->Channel() != mCurrentChannel)
            {
               SwitchToChannel(item->Channel());
               switched = true;

               if (mData->mHidePluginAfterChannelSwitch)
                  return osEnd;
            }

            break;
         }
         default:
         {
            if (mData->mSwitchChannelOnItemSelect && item && item->Channel() 
                && item->Channel() != mCurrentChannel)
            {
               SwitchToChannel(item->Channel());
               switched = true;

               if (mData->mHidePluginAfterChannelSwitch)
                  return osEnd;
            }

            break;
         }
      }

      if (Key == mData->mHideButton && !switched)
         state = osEnd;
   }
   
   return state;
}

void cMenuDirector::SwitchToChannel(const cChannel* channel)
{
  if (!channel)
    return;

  cDevice::PrimaryDevice()->SwitchChannel(channel, true);
  mCurrentChannel = channel;
}

void cMenuDirector::DetermineReferenceChannel()
{
  cChannel* currentChannel = Channels.GetByNumber(cDevice::PrimaryDevice()->CurrentChannel());

  if (!mReferenceChannel)
  {
    const cChannel* referenceChannel = currentChannel;
	
    if(!GetLinkChannelsOutOfChannel(referenceChannel))
      referenceChannel = GetRefChannelOutOfChannel(referenceChannel);
	
    mReferenceChannel = referenceChannel;
  }

  mCurrentChannel = currentChannel;
}

void cMenuDirector::AddEpgItem(const cEvent* event)
{
   Add(new cOsdItem("-------------------------------------------------------------------------------"));
   cList<cOsdItem>::Last()->SetSelectable(false);

   if (mData->mDisplayEpgInfo == 1)
   {
      char* title2;
      
      if (event && event->Title() && *event->Title())
         asprintf(&title2, "%s  -  %d'", event->Title(), event->Duration()/60);
      else
         asprintf(&title2, "%s", tr("No EPG information available"));
      
      Add(new cOsdItem(title2));
      free(title2);
      cList<cOsdItem>::Last()->SetSelectable(false);
   }
}

void cMenuDirector::Update()
{
   if (mReferenceChannel != NULL)
   {
#if VDRVERSNUM >= 10326
      int noOfChannels = 1;
      if(GetLinkChannelsOutOfChannel(mReferenceChannel))
         noOfChannels += GetLinkChannelsOutOfChannel(mReferenceChannel)->Count();
      SetCols(11, 3, 8, 6);
#endif

      SetHasHotkeys();
      Add(new cMenuDirectorItem(this, mReferenceChannel));
    
      if (GetLinkChannelsOutOfChannel(mReferenceChannel))
      {
         char* lastTitle = 0;
         const cLinkChannels* linkChannels = GetLinkChannelsOutOfChannel(mReferenceChannel);
       
         dsyslog("Updating director menu with (%d) linked channels", linkChannels->Count());

         for (cLinkChannel* linkChannel = linkChannels->First(); linkChannel; 
              linkChannel = linkChannels->Next(linkChannel))
         {
            const char* title = "";
            const cEvent* event = 0;
            const cEvent* eventNext = 0;
            cSchedulesLock schedulesLock;
            const cSchedules* schedules = cSchedules::Schedules(schedulesLock);
            const cSchedule* schedule = schedules->GetSchedule(linkChannel->Channel()->GetChannelID());

            if (schedule)
            {
               eventNext = schedule->GetFollowingEvent();
               event = schedule->GetPresentEvent() ? schedule->GetPresentEvent() : eventNext;

               title = event ? event->Title() : "";
            }

            if (!event)
               continue;

            if (!lastTitle || strcmp(title, lastTitle) != 0)
            {
               AddEpgItem(event);

               if (lastTitle) free(lastTitle);
               asprintf(&lastTitle, "%s", title);
            }

            Add(new cMenuDirectorItem(this, linkChannel->Channel(), event, true));

            if (schedule)
            {
               for (int i = 0; i < mData->mShowNext; i++)
               {
                  event = schedule->Events()->Next(event);
                  
                  if (!event || strcmp(event->Title(), lastTitle ? lastTitle : "") != 0)
                     break;
                  
                  Add(new cMenuDirectorItem(this, linkChannel->Channel(), event, true, true));
               }
            }
         }
      }
   }
   else if (mCurrentChannel != NULL)
   {
#if VDRVERSNUM >= 10326
      SetCols(mData->mDisplayShortcutNumbers ? 2 : 0);
#endif
      Add(new cMenuDirectorItem(this, mCurrentChannel));
   }

   SelectCurrentChannel();
}

void cMenuDirector::SelectCurrentChannel()
{
  for (cMenuDirectorItem* item = (cMenuDirectorItem*)First(); item; 
       item = (cMenuDirectorItem*)Next(item))
  {
    if (item->Channel() == mCurrentChannel)
    {
      SetCurrent(item);
      break;
    }
  }
}

eOSState cMenuDirector::Record(cMenuDirectorItem* item)
{
   int match = tmNone;

   if (!item)
      return osContinue;

   Timers.GetMatch(item->Event(), &match);

   if (match == tmFull) 
   {
      int tm = tmNone;
      cTimer* timer = Timers.GetMatch(item->Event(), &tm);

      if (timer)
         return AddSubMenu(new cMenuEditTimer(timer));
   }

   cTimer* timer = new cTimer(item->Event());
   cTimer* t = Timers.GetTimer(timer);

   if (t) 
   {
      delete timer;
      timer = t;

      return AddSubMenu(new cMenuEditTimer(timer));
   }
   else 
   {
      Timers.Add(timer);
      Timers.SetModified();
      isyslog("timer %s added (active)", *timer->ToDescr());

      if (timer->Matches(0, false, 120))
         return AddSubMenu(new cMenuEditTimer(timer));

      SetHelpKeys(item);
   }

   return osContinue;
}

void cMenuDirector::SetHelpKeys(cMenuDirectorItem* item)
{
  const char* Red[] = { NULL, trVDR("Button$Record"), trVDR("Button$Timer") };
  int helpKeys = 1;

  if (item && item->Event())
  {
     int match = tmNone;

     Timers.GetMatch(item->Event(), &match);
     
     if (match == tmFull)
        helpKeys = 2;
  }

  SetHelp(Red[helpKeys], 0, tr("Switch View"), trVDR("Button$Info"));

  if (item && item->Update(false))
     Display();
}

const cLinkChannels* cMenuDirector::GetLinkChannelsOutOfChannel(const cChannel* channel)
{
   if (!channel)
      return NULL;
   
#if VDRVERSNUM < 10333
   return channel->GetLinkChannels();
#else
   return channel->LinkChannels();
#endif
}

const cChannel* cMenuDirector::GetRefChannelOutOfChannel(const cChannel* channel)
{
   if (!channel)
      return NULL;
   
#if VDRVERSNUM < 10333
   return channel->GetRefChannel();
#else
   return channel->RefChannel();
#endif
}
