#!/usr/bin/perl
#################################################
# Package that contains the main radio          #
# application                                   #
#################################################
use strict;
use LCD;
use RadioControls;
use Menu;
use MPC;
use LWP::Simple;
use XML::RSS::LibXML;


package WLAN::Radio::Application;
use Time::HiRes qw(usleep nanosleep);

# Constructor for WLAN radio application
sub new {
   my $invocant = shift;
   my $level = 0;   
   my $self = {};


   $self->{'menu'} = new WLAN::Radio::Menu();
   $self->{'display'} = new BWCT::LCD();
   $self->{'MPC'} = new WLAN::Radio::MPC();
   
   # Configuration stuff
   $self-> {'maxNrOfPodcasts'} = 	10; # Max number of podcast episodes that should be extracted from feed & displayed
   $self-> {'streamFile'} = 		"/boot/config/radiostations.js"; # Change to your current CSV file with radio streams (in the form "title,url")
   $self-> {'mp3Dir'} = 		"/music/MusicBox/"; # Change to your current MP3 folder
   $self-> {'controls'} = new WLAN::Radio::Controls();   

   my $class=ref($invocant) || $invocant;   
    
   bless $self, $class;
   return $self;
}

#
# Global initialisation
#
sub init {
   my $self = shift;
   $self->{'display'}->init();
   if ($self->{'MPC'}->getStatus()->{'playingState'} ne 'STOPPED') {
         $self->{'MPC'}->stop();
         usleep(500);
         return($self->init());
   }
   
   $self->{'display'}->display(0, "Willkommen!", 1,1,"","");
   # Add static menus
   $self->createTopMenu();
   $self->{'controls'}->checkState();
   $self->{'menu'}->{'current'} = $self->{'controls'}->{'source'}; 
   $self->displayMenu();
   $self->{'inMenu'}  = 1;
   $self->{'inDialog'} = 0;
   $self->{'running'} = 1;
   #$self->{'controls'}->setSource(0);
   
   $self->beginDispatching();
   
}

#
# Display the current menu
#
sub displayMenu () {
   my $self = shift;
   $self->{'display'}->display(1,
		$self->{'menu'}->getCurrent()->{'name'},
		1,
		1,
		($self->{'menu'}->hasPrev() == 1) ? "< " : "",
		($self->{'menu'}->hasNext() == 1) ? " >" : ""
  );
}

#
# Displays a "yes/no" dialog
# 
sub displayDialog () {
   my $self = shift;
   my $question = shift;
   $self->{'display'}->display(0,$question,0,1,"","");
   $self->{'display'}->display(1,"Ja  /  Nein",0,1,"","");
}


#
# Creates the top level menu
#
sub createTopMenu () {
   my $self = shift;
   $self->{"streamMenu"} = $self->createRadioMenu("Radio",$self-> {'streamFile'});
   $self->{"podcastMenu"} = $self->createPodcastsMenu("Podcasts");
   $self->{"mp3Menu"} = new WLAN::Radio::Menu("MP3s");
   $self->{"mp3Menu"}->{'folder'} = $self-> {'mp3Dir'};
   $self->{"menu"}->addSubMenu($self->{"streamMenu"});
   $self->{"menu"}->addSubMenu($self->{"podcastMenu"});
   $self->{"menu"}->addSubMenu($self->{"mp3Menu"});
}

sub createPodcastsMenu {
  my $self = shift;
  my $title = shift;
  my $podcasts = $self->{'MPC'}->ls("Podcasts/Subscribed Feeds");
  my $menu = new WLAN::Radio::Menu($title);
  foreach my $podcast (@{$podcasts}) {
     my $title = $podcast;
     $title =~ s/^Podcasts\/Subscribed Feeds\///;
     #print "Adding $title\n";
     my $episodes = $self->{'MPC'}->ls($podcast);
     my $len = @{$episodes};
     my $podcastMenu = -1;
     if ($len > 0) {
        my @segs = split('#', $episodes->[0]);
  	my $podcastMenu = new WLAN::Radio::Menu($title); 
        $segs[0] =~ s/^podcast://;
        $podcastMenu->{'podcast'} = $segs[0];
        #print "URL : $segs[0]\n\n";   
        $menu->addSubMenu($podcastMenu);
     }
  }
  return($menu);
}

sub createRadioMenu {
   my $self = shift;
   my $title =shift;
   my $file = shift;

   my $menu = new WLAN::Radio::Menu($title);
   open (FILE, "< ".$file) or return($menu);
   my @lines = <FILE>;
   chomp(@lines);
   close (FILE);
   foreach my $line(@lines) {
      if ($line =~ /radioStations\.push/) {
         my ($entry) = $line =~ /\[(.*?)\]/; 
         my @segs = split (',', $entry);
         my ($title) = $segs[0] =~ /.*?'(.*?)'/;
	 my ($url) = $segs[1] =~ /.*?'(.*?)'/;
         $menu->addEntry({"name" => $title, "stream" => $url});
      }
   }
   return ($menu);
}
#
# Creates a menu of webradio streams or podcasts
#
sub createURLMenu {
   my $self = shift;
   my $type = shift;
   my $title = shift;
   my $file = shift;
   my $menu = new WLAN::Radio::Menu($title);
   open (FILE, "< ".$file) or return($menu);
   my @streams = sort(<FILE>);
   close(FILE);
   chomp (@streams);
   foreach my $stream (@streams) {
      my ($title, $url) = split(",", $stream);
      if ($type eq "podcast") {
          #print "Adding $title\n";
	  my $podcastMenu = new WLAN::Radio::Menu($title);
          $podcastMenu->{'podcast'} = $url; 
          $menu->addSubMenu($podcastMenu);
      }
      else {
         $menu->addEntry({"name" => $title, "$type" => $url});
      }
      #print "Adding $title\n";
   }
   return($menu);
}


#  
# Begin Dispatch Loop
#
sub beginDispatching () {
   my $self = shift;
   my $tickCnt = 0;
   my $TIME_ACT = time();
   while (1) {     
     if ($self->{'running'} == 1) {
	     # Requesting MPC state
	     my $state = $self->{'MPC'}->getStatus();
	     # GPIO polling
	     my $event = $self->{'controls'}->checkState();
	     $self->dispatchAction ($event,$state);
	     $state = $self->{'MPC'}->getStatus();
	     # Updating display
	     if ($TIME_ACT != time()) {
		if ($state->{'playingState'} eq "PLAYING") {
		   # Title has not changed we simply update playing time
		   if ($state->{'title'} eq $self->{'display'}->{'lines'}[1]->{'text'}) {
			   $self->updatePlayingTime();
		   }
		   # Whole display needs to be changed
		   else {
			$self->displayMPCState();
		   }
		}
		 # Check whether song has stopped
		elsif (($state->{'playingState'} eq "STOPPED") && ($self->{'inMenu'}  == 0) && ($self->{'inDialog'} == 0)) {
			$self->{'display'}->display(0, "Stopped", 1,1);
			# Back to the menu
			$self->{'inMenu'}  = 1;
			$self->displayMenu();
		}
		if ($tickCnt % 2 == 0) 	{
			$self->{'display'}->tick();
		}
		$tickCnt = ($tickCnt +1) % 2; 
		$TIME_ACT = time();
	     }
     }
     usleep(50);
  }
}

#
# Reacts on user action depending on mouse event
#
sub dispatchAction () {
   my $self = shift;
   my $mouseEvent = shift;
   my $mpcState = shift;
   #print $mouseEvent,"\n";
     
   if ($mouseEvent eq "") {return;}
   if ($mouseEvent eq "source") {
   	$self->{'MPC'}->stop();
        $self->{'display'}->display(0, "Stopped", 1,1);
        # Back to the menu
        $self->{'inMenu'}  = 1;
        while ($self->{'menu'}->getParent() != -1) {
		$self->{'menu'} = $self->{'menu'}->getParent();
	}
        $self->{'menu'}->{'current'} = $self->{'controls'}->{'source'};
        $self->displayMenu();
        return;
   }
   #if (($self->{'inMenu'} == 1) && ($self->{'menu'}->getParent() == -1)) {
   #   return;
   #}

   # ScrollUp: Menu->left / Rewind / Decrease volume
   if ($mouseEvent eq "btn_left") {
      
      # Rewind
      if  ($mpcState->{'playingState'} eq 'PAUSED') {
          my $seekPos = $self->{'MPC'}->seek_async("-00:10");
	  $self->{'display'}->display(0,
			"Go To $seekPos",
			1,
			1
	 );
      }
      # Volume control
      #elsif  ($mpcState->{'playingState'} eq 'PLAYING') {
      #   my $status = $self->{'MPC'}->decVolume();
      #   $self->{'display'}->display(0,
      #			"Volume: ".$status->{'volume'},
      #			1,
      #			1
      #	 );
      #}
      
      elsif  ($mpcState->{'playingState'} eq 'PLAYING') {
	if ($self->{'controls'}->{'source'} == 0) {
	   if ($self->{'menu'}->hasPrev() == 1) {
		   $self->{'MPC'}->stop();
		   $self->{'inMenu'}  = 1;
		   $self->{'menu'}->prev();   
		   $self->displayMenu();
                   $self->executeMenuAction();
	   }
        }
      }


      # Menu
      elsif ($self->{'inMenu'} == 1) {
         # Menu action: Scroll to left
         if ($self->{'menu'}->{'level'} > 0) {
	 	$self->{'menu'}->prev();   
         	$self->displayMenu();
         
	 
	 }
      }
   }
   
   # Scroll down: Menu->right / Fast Forward / Increase volume
   elsif ($mouseEvent eq "btn_right") {
      
      # Fast forward
      if  ($mpcState->{'playingState'} eq 'PAUSED') {
         my $seekPos = $self->{'MPC'}->seek_async("+00:10");
	 $self->{'display'}->display(0,
			"Go To $seekPos",
			1,
			1
	 );
      }
      # Volume control
      #elsif  ($mpcState->{'playingState'} eq 'PLAYING') {
      #   my $status = $self->{'MPC'}->incVolume();
      #   $self->{'display'}->display(0,
      #			"Volume: ".$status->{'volume'},
      #			1,
      #			1
      #	 );
      #}

      elsif  ($mpcState->{'playingState'} eq 'PLAYING') {
	if ($self->{'controls'}->{'source'} == 0) {
           if ($self->{'menu'}->hasNext() == 1) {
		   $self->{'MPC'}->stop();
		   $self->{'inMenu'}  = 1;
	           $self->{'menu'}->next();   
	           $self->displayMenu();
		   $self->executeMenuAction();
           }
        }
      }

      #Menu
      elsif ($self->{'inMenu'} == 1) {
         # Menu action: Scroll to right
	if ($self->{'menu'}->{'level'} > 0) {
         $self->{'menu'}->next();
         $self->displayMenu();
        
	 }
      }
   }

   # Menu: Execute / Pause / Resume
   elsif ($mouseEvent eq "btn_ok") {
      if ($self->{'inDialog'} ==1) {
         `shutdown -h now`;
          $self->{'display'}->display(0,
			"Tschuess...",
			1,
			1
	  );
         $self->{'display'}->flush(1);
         $self->{'running'} = 0;
         return;
      }
      
      elsif ($mpcState->{'playingState'} eq 'PLAYING') {
        # We are playing something, let's pause
        $self->displayMPCState ($self->{'MPC'}->pause());
      }
      elsif ($mpcState->{'playingState'} eq 'PAUSED') {
        # We are currently on pause, lets resume playing
        my $seeked = $self->{"MPC"}->executePendingActions();
	if ($seeked > 0) {        
		$self->displayMPCState ();
        }
        else {
         $self->displayMPCState ($self->{'MPC'}->play());
        }
      }
      elsif ($self->{'inMenu'} == 1) {
        # Execute an action that is defined for this menu entry         
	$self->executeMenuAction();
      }
   }

   # Menu: Go up a level / Stop
   elsif ($mouseEvent eq "btn_cancel") {
      if ($self->{'inDialog'} ==1) {
           $self->{'inDialog'} = 0;
           $self->{'inMenu'} = 1;
	   $self->displayMenu();
	   $self->{'display'}->display(0,
			"Categories:",
			1,
			1
		);
           return;
      }
      if (($mpcState->{'playingState'} eq 'PLAYING') || ($mpcState->{'playingState'} eq 'PAUSED')) {
        # We're playing some files. Stop this.
        $self->{'MPC'}->stop();
        $self->{'display'}->display(0, "Stopped", 1,1);
        # Back to the menu
        $self->{'inMenu'}  = 1;
        $self->displayMenu();      
      }
      elsif ($self->{'inMenu'} == 1) {
         if ($self->{'menu'}->{'level'} > 0) {
            $self->{'menu'} = $self->{'menu'}->getParent();
            $self->displayMenu();
         }
         else {
            $self->displayDialog ("Ausschalten?");
            $self->{'inMenu'} = 0;
            $self->{'inDialog'} = 1;
            return;
         }
      }
     
     if ($self->{"menu"}->{'level'} == 0) {
		$self->{'display'}->display(0,
			"Categories:",
			1,
			1
		);  
        } 
     }
}

#
# Executes the action assigned to the current menu item
#
sub executeMenuAction () {
   my $self = shift;

   # -- Open MP3 folder
   if (exists(${$self->{"menu"}->getCurrent()}{'folder'})) {       
        $self->{"menu"}->getCurrent()->reset();
        $self->{"menu"}->getCurrent()->flush();
	# This may take some time, so we inform the user about it
        $self->{'display'}->display(0,
		"Just a moment",
		1,
		1
        );
        my $folder = $self->{"menu"}->getCurrent()->{'folder'};
        opendir(MP3FOLDER, $folder);
        my @files = readdir(MP3FOLDER) or return;
        my @sorted = sort(@files);
        close (MP3FOLDER);
        foreach my $file (@sorted) {
           if ($file !~ /^\./) {
              if (-d $folder."/".$file) {
                 # Create a new menu from the folder name   
		 my $folderMenu = new WLAN::Radio::Menu($file);
                 $folderMenu->{'folder'} = $folder."/".$file;
                 $self->{"menu"}->getCurrent()->addSubMenu($folderMenu);
              }
              else {
                 $self->{"menu"}->getCurrent()->addEntry({"name" => $file, "mediaFile" => $folder."/".$file});
              }
           }
        }
        $self->{'display'}->display(0,
		"MP3s:",
		1,
		1
        );
   }
   # Play MP3 File
   elsif (exists(${$self->{"menu"}->getCurrent()}{'mediaFile'})) {
      if ($self->{'MPC'}->getStatus()->{'playingState'} ne 'STOPPED') {
          $self->{'MPC'}->stop();
      }
      $self->{'MPC'}->clear();
      $self->{'MPC'}->add("file://". $self->{"menu"}->getCurrent()->{'mediaFile'});
      $self->{'inMenu'}  = 0;
      $self->displayMPCState($self->{'MPC'}->play());
   }
   # Play Stream
   elsif (exists(${$self->{"menu"}->getCurrent()}{'stream'})) {
      if ($self->{'MPC'}->getStatus()->{'playingState'} ne 'STOPPED') {
          $self->{'MPC'}->stop();
      }
      $self->{'MPC'}->clear();
      
      # This may take some time, so we inform the user about it
      $self->{'display'}->display(0,
		"Just a moment",
		1,
		1
      );

      # Loading streams from M3U files
      if ($self->{"menu"}->getCurrent()->{'stream'} =~ /\.m3u$/i) {
          my $content = LWP::Simple::get($self->{"menu"}->getCurrent()->{'stream'});
          my @lines = split('\n', $content);
          foreach my $stream (@lines) {
	      $self->{'MPC'}->add($stream);
          }
      }
      else {
          $self->{'MPC'}->add($self->{"menu"}->getCurrent()->{'stream'});
      }

      $self->{'inMenu'}  = 0;
      $self->{'MPC'}->play();
  
      #sleep(2);
      my $state = $self->{'MPC'}->getStatus();
      $state->{'title'} = $self->{"menu"}->getCurrent()->{'name'};
      $self->displayMPCState($state);
   }
   # Browse podcast
   elsif (exists(${$self->{"menu"}->getCurrent()}{'podcast'})) {
        $self->{"menu"}->getCurrent()->reset();
        $self->{"menu"}->getCurrent()->flush();
        $self->{'display'}->display(0,
		"Just a moment",
		1,
		1
        );
        # Fetching RSS feed
        my $rss = $self->{"menu"}->getCurrent()->{'podcast'};
        my $p = XML::RSS::LibXML->new;
        my $feed = $p->parse(LWP::Simple::get($rss));
        my $cnt = 0;
        for my $item ($feed->items) {
            if ($cnt <= $self->{'maxNrOfPodcasts'}) {
               my $title = $item->{"title"};                       
	       my $enclosure = $item->{"enclosure"};
               if ($enclosure) {
	   	   my $url = $enclosure->{"url"}; 
	           #my $url = $attribs->{'{}url'};
                   #print "$title / $url\n";
                   $self->{"menu"}->getCurrent()->addEntry({"name" => $title, "stream" => $url});
               }
               
            }
            $cnt++;
        }
        $self->{'display'}->display(0,
		"Podcasts:",
		1,
		1
        );
   }
        

   # -- Open Sub menu
   if ($self->{"menu"}->getCurrent()->{'isMenu'} == 1) {        
	$self->{"menu"}->getCurrent()->reset();
        $self->{"menu"} = $self->{"menu"}->getCurrent();
        $self->displayMenu();
   }
}

#
# Display Status of MPC in display
#
sub displayMPCState {
   my $self = shift;
   my $status = shift || $self->{'MPC'}->getStatus();

   my $song = $status->{'title'};
   $self->{'display'}->display(1,
		$song,
		1,
		1
  );
  $self->updatePlayingTime($status);
 
}
#
# Updates the playing time in the display
#
sub updatePlayingTime {
   my $self = shift;
   my $status = shift || $self->{'MPC'}->getStatus();
   my $playingState = "";
   if ($status->{'playingState'} eq "PLAYING") {$playingState = "Playing ";}
   elsif ($status->{'playingState'} eq "PAUSED") {$playingState = "Paused ";}

   $self->{'display'}->display(0,
		$playingState.$status->{'time'},
		1,
		1
  );
}
   
  
1;

  

