/*
 * pin.c: A plugin for the Video Disk Recorder
 *
 * See the README file for copyright information and how to reach the author.
 *
 * Date: 11.04.05 - 10.12.05, horchi 
 */

//***************************************************************************
// Includes
//***************************************************************************

#include <vdr/interface.h>
#include <vdr/videodir.h>
#include <vdr/menu.h>

#include "pin.h"
#include "menu.h"
#include "setupmenu.h"

//***************************************************************************
// Pin
//***************************************************************************
//***************************************************************************
// Object
//***************************************************************************

cPin::cPin(void)
{
   pinTxt = "Pin: ";
   osd = 0;
   textWidth = cFont::GetFont(fontOsd)->Width(pinTxt);
   clear();
}

cPin::~cPin()
{
  delete osd;
}

//***************************************************************************
// Clear
//***************************************************************************

void cPin::clear()
{
   *code = 0;
   cOsd::pinValid = false;
}

//***************************************************************************
// Show
//***************************************************************************

void cPin::Show(void)
{
   tArea area;
   tArea A = { 0, 0, 200, 30,  4 };

   osd = cOsdProvider::NewOsd(50, 50);

   if (osd) 
   {
      clear();

      memcpy(&area, &A, sizeof(tArea));

      while ((area.Width() & (8/area.bpp-1)) != 0)
        area.x2++;
         
      osd->SetAreas(&area, 1);
      osd->DrawRectangle(0, 0, area.x2-area.x1, area.y2-area.y1, clrBlue);
      osd->DrawText(2, 5, pinTxt, clrYellow, clrBlue, cFont::GetFont(fontOsd));

      osd->Flush();
   }
}

//***************************************************************************
// Process Key
//***************************************************************************

eOSState cPin::ProcessKey(eKeys Key)
{
   int len = strlen(code);
   eOSState state = cOsdObject::ProcessKey(Key);
  
  (cPinPlugin::getObject())->checkActionTime();

   if (state == osUnknown)
   {
      switch (Key)
      {
         case k0 ... k9: 
         {
            if (len >= 20)
               break;
            
            code[len++] = Key-k0+48;
            code[len] = 0;
            break;
         }
         
         case kOk:     
         {
            if (strcmp(code, cPinPlugin::pinCode) == 0)
               cOsd::pinValid = yes;
            else
               cOsd::pinValid = no;
            
            state = osEnd;
            break;
         }

         default: return state;
      }
      
      if (state == osEnd)
      {
         osd->DrawText(2 , 5,  cOsd::pinValid ? "Code accepted" : "Invalid Code !", 
                       cOsd::pinValid ? clrGreen : clrRed, clrBlue, cFont::GetFont(fontOsd));
         osd->Flush();
         sleep(1);
      }
      else
      {
         osd->DrawText(2 + textWidth + cFont::GetFont(fontOsd)->Width(code), 
                       5, "*", clrYellow, clrBlue, cFont::GetFont(fontOsd));
         osd->Flush();
         state = osContinue;
      }
   }
   
   return state;
}

//***************************************************************************
// PIN Plugin
//***************************************************************************
//***************************************************************************
// Static Stuff
//***************************************************************************

char cPinPlugin::pinCode[] = DEFAULT_PIN;
cPinPlugin* cPinPlugin::object = 0;
bool cPinPlugin::skipChannelSilent = no;
int cPinPlugin::pinResetTime = 90;                        // default 1,5 hour
int cPinPlugin::autoProtectionMode = apmIntelligent;

const char* cPinPlugin::autoProtectionModes[] = 
{
   "always",
   "intelligent",
   "never",
   0
};

//***************************************************************************
// Object
//***************************************************************************

cPinPlugin::cPinPlugin(void)
{
   statusMonitor = 0;
   lastAction = time(0);
   receiver = 0;
}

cPinPlugin::~cPinPlugin()
{
   if (receiver) delete receiver;
   if (statusMonitor) delete statusMonitor;
}

//***************************************************************************
// Initialize
//***************************************************************************

bool cPinPlugin::Initialize(void)
{
   char* path;

   asprintf(&path, "%s/%s", cPlugin::ConfigDirectory(), "pin");

   if (!DirectoryOk(path, no))
   {
      if (mkdir(path, 770))
      {
         // cannot create the directory

         esyslog("Creating of directory '%s' failed, errno was (%d) - '%s'", 
                 path, errno, strerror(errno)); 
         free(path);

         return true; // anyhow let vdr start
      }
   }

   free(path);

   lockedChannels.Load(AddDirectory(cPlugin::ConfigDirectory(), 
                                    "pin/channellocks.conf"), true, false);
   lockedBroadcasts.Load(AddDirectory(cPlugin::ConfigDirectory(),
                                      "pin/broadcastlocks.conf"), true, false);
   lockedPlugins.Load(AddDirectory(cPlugin::ConfigDirectory(),
                                      "pin/pluginlocks.conf"), true, false);

   return true;
}

//***************************************************************************
// Main Menu Action
//***************************************************************************

cOsdObject *cPinPlugin::MainMenuAction(void)
{
   initPluginList();

   if (cOsd::pinValid)
      return new cPinMenu(tr(MAINMENUENTRY), &lockedChannels, 
                          &lockedBroadcasts, &lockedPlugins);

   return new cPin();
}

//***************************************************************************
// Start
//***************************************************************************

bool cPinPlugin::Start(void)
{
   statusMonitor = new cPinStatusMonitor;
   object = this;
   RegisterI18n(Phrases);

   receiver = new MessageReceiver;
   receiver->StartReceiver();

   return true;
}

//***************************************************************************
// Stop
//***************************************************************************

void cPinPlugin::Stop(void)
{
   receiver->StopReceiver();
}

//***************************************************************************
// Setup Menu
//***************************************************************************

cMenuSetupPage* cPinPlugin::SetupMenu(void)
{
   if (cOsd::pinValid)
      return new PinSetupMenu;

   Skins.Message(mtInfo, tr("Please enter pin code first"));

   return 0;
}

//***************************************************************************
// Setup Parse
//***************************************************************************

bool cPinPlugin::SetupParse(const char *Name, const char *Value)
{
   // Pase Setup Parameters
   
   if (!strcasecmp(Name, "pinCode"))
      strcpy(pinCode, Value);
   else if (!strcasecmp(Name, "skipChannelSilent"))
      skipChannelSilent = atoi(Value);
   else if (!strcasecmp(Name, "pinResetTime"))
      pinResetTime = atoi(Value);
   else if (!strcasecmp(Name, "autoProtectionMode"))
      autoProtectionMode = atoi(Value);
   else
      return false;
   
   return true;
}

//***************************************************************************
// Test if Channel Protected
//***************************************************************************

int cPinPlugin::channelProtected(const char* name, long startTime)
{
   return lockedChannels.Locked(name, startTime);
}

//***************************************************************************
// Test if Plugin Protected
//***************************************************************************

int cPinPlugin::pluginProtected(const char* name)
{
   return lockedPlugins.Locked(name);
}

//***************************************************************************
// Test if Broadcast Protected
//***************************************************************************

int cPinPlugin::broadcastProtected(const char* title)
{
   return lockedBroadcasts.Locked(title);
}

//***************************************************************************
// Check Action Time
//***************************************************************************

void cPinPlugin::checkActionTime()
{
   time_t lNow = time(0);

   if (pinResetTime > 0 && lNow > lastAction+(pinResetTime*60) && cOsd::pinValid == true)
   {
      // keine User Aktion, Kindersicherung aktivieren ..

      dsyslog("Pin: Locking pin due to no user action since (%ld) seconds", 
              lNow-lastAction);

      cOsd::pinValid = false;
   }

   lastAction = lNow;
}

//***************************************************************************
// Init Plugin List
//***************************************************************************

int cPinPlugin::initPluginList()
{
   static int initialized = no;
   cPlugin* plugin;
   int i = 0;
   int cnt = 0;

   if (initialized)
      return done;

   initialized = yes;

   while ((plugin = cPluginManager::GetPlugin(i)))
   {
      if (!lockedPlugins.FindByName(plugin->Name())
          && strcasecmp(plugin->Name(), "pin") != 0)
      {
         lockedPlugins.Add(new cLockItem(plugin->Name(), no));
         cnt++;
         dsyslog("PIN added plugin (%d) '%s' - '%s'", 
                 i, plugin->Name(), plugin->MainMenuEntry());
      }

      i++;
   };

   if (cnt)
      lockedPlugins.Save();

   return done;
}

//***************************************************************************
// Channel Switch
//***************************************************************************

void cPinStatusMonitor::ChannelSwitch(const cDevice *Device, int ChannelNumber)
{
   // cChannel* channel = Channels.GetByNumber(ChannelNumber);

   (cPinPlugin::getObject())->checkActionTime();
}

//***************************************************************************
// Replay Protected
//***************************************************************************

bool cPinStatusMonitor::ReplayProtected(const cRecording* Recording, const char* Name, 
                                        const char* Base, bool isDirectory)
{
   bool fProtected;
   struct stat ifo;
   char* path = 0;
   char* base = 0;
   char* name = 0;

   if (cOsd::pinValid)
      return false;

   (cPinPlugin::getObject())->checkActionTime();

   if (Base)
   {
      base = strdup(Base);
      base = ExchangeChars(base, true);
   }

   if (Name) 
   {
      name = strdup(Name);
      name = ExchangeChars(name, true);
   }

   dsyslog("checking protection of %s '%s%s%s'", 
           isDirectory ? "directory" : "recording", 
           isDirectory && base ? base : "",
           isDirectory && base ? "/" : "",
           isDirectory ? name : Recording->Name());

   if (isDirectory)
      asprintf(&path, "%s%s%s/%s/%s", 
               VideoDirectory, 
               base ? "/" : "",
               base ? base : "",
               name ? name : "",
               PROTECTION_FILE);
   else
      asprintf(&path, "%s/%s", 
               Recording->FileName(), 
               PROTECTION_FILE);

   isyslog("checking rights, protection file is '%s'", path);

   fProtected = stat(path, &ifo) == 0;

   if (path) free(path);
   if (base) free(base);
   if (name) free(name);

   if (fProtected)
   {
      Skins.Message(mtError, tr("Recording protected, enter pin code first"));

      return true;
   }

   return false;
}

//***************************************************************************
// Channel Protected
//***************************************************************************

bool cPinStatusMonitor::ChannelProtected(const cDevice *Device, const cChannel* Channel)
{
   char* buf;
   const cEvent* event;

   if (cOsd::pinValid)
      return false;

   if (!Channel || !Channel->Name() || Channel->GroupSep())
      return false;

   // first check channel protection

   dsyslog("checking protection of channel '%s', '%s'", 
           Channel->Name(),
           Channel->ShortName());

   if ((cPinPlugin::getObject())->channelProtected(Channel->Name()))
   {
      if (cPinPlugin::skipChannelSilent)
         return true;

      dsyslog("channel %d '%s' is protected", Channel->Number(), Channel->Name());
      asprintf(&buf, "%d %s - %s", Channel->Number(), Channel->Name(), tr("channel is protected"));
      Skins.Message(mtInfo, buf);
      
      free(buf);

      return true;
   }

   // second check broadcast protection

   if (!(event = GetEventOf(Channel)))
      return false;

   if ((cPinPlugin::getObject())->broadcastProtected(event->Title()))
   {
      if (cPinPlugin::skipChannelSilent)
         return true;

      dsyslog("broadcast '%s' is protected", event->Title());
      asprintf(&buf, "%s - %s", event->Title(), tr("broadcast is protected"));
      Skins.Message(mtInfo, buf);
      
      free(buf);

      return true;
   }

   return false;
}

//***************************************************************************
// Recording
//***************************************************************************

void cPinStatusMonitor::RecordingFile(const char* FileName) 
{
   char* path = 0;
   cRecordControl* rc;
   FILE* f;

   if (!FileName || !strlen(FileName))
      return;

   rc = cRecordControls::GetRecordControl(FileName);

   dsyslog("checking if autoprotection for '%s' is needed", FileName);

   if (rc && rc->Timer() && rc->Timer()->Channel())
   {
      if (rc->Timer()->FskProtection())
      {
         asprintf(&path, "%s/%s", FileName, PROTECTION_FILE);
         
         dsyslog("autoprotecting recording due to '%s'; channel '%s'; file '%s'", 
                 rc->Timer()->FskProtection() ? "timer configuration" : "of protected channel", 
                 rc->Timer()->Channel()->Name(), path);

         f = fopen(path, "w");
         if (f) fclose(f);

         free(path);
      }
   }
}

//***************************************************************************
// Get Event Of
//***************************************************************************

const cEvent* cPinStatusMonitor::GetEventOf(const cChannel* Channel)
{
   cSchedulesLock schedLock;
   const cSchedules* scheds;
   const cSchedule *sched;
   const cEvent* event;

   if (!(scheds = cSchedules::Schedules(schedLock)))
      return 0;
   
   if (!(sched = scheds->GetSchedule(Channel->GetChannelID())))
      return 0;
   
   if (!(event = sched->GetPresentEvent()))
      return 0;

   return event;
}

//***************************************************************************
// Timer Creation
//***************************************************************************

void cPinStatusMonitor::TimerCreation(cTimer* Timer, const cEvent* Event)
{
   int fsk = 0;

   dsyslog("Timer Creation");

   if (!Timer || !Event || !Timer->Channel())
      return;

   dsyslog("Timer Creation for event '%s'", Event->Title());
   
   switch (cPinPlugin::autoProtectionMode)
   {
      case cPinPlugin::apmAlways:  fsk = 1; break;
      case cPinPlugin::apmNever:   fsk = 0; break;

      case cPinPlugin::apmIntelligent:
      {
         fsk = (cPinPlugin::getObject())->channelProtected(
            Timer->Channel()->Name(), Event->StartTime())
            || (cPinPlugin::getObject())->broadcastProtected(Event->Title());

         break;
      }
   }

   Timer->SetFskProtection(fsk);
}

//***************************************************************************
// Plugin Protected
//***************************************************************************

bool cPinStatusMonitor::PluginProtected(cPlugin* Plugin)
{
   char* buf;

   if (cOsd::pinValid)
      return false;

   if (!Plugin)
      return false;

   // check if plugin is protected

   dsyslog("checking protection of plugin '%s'", Plugin->MainMenuEntry());

   if ((cPinPlugin::getObject())->pluginProtected(Plugin->Name()))
   {
      dsyslog("plugin '%s' is protected", Plugin->MainMenuEntry());
      asprintf(&buf, "Plugin %s %s", Plugin->MainMenuEntry(), tr("is protected"));
      Skins.Message(mtInfo, buf);
      
      free(buf);

      return true;
   }

   return false;
}

//***************************************************************************

VDRPLUGINCREATOR(cPinPlugin);
