# Guide Data Checker (GPL'd)
#
# 01/03/2005 - v1.0.1 - Original version from mrtickle's module
# 23/03/2005 - v1.0.2 - Added support to display gaps and missing data
# 04/04/2003 - v1.0.3 - Now handles channels with no data
set gd_expected 0
proc action_gdchecker {chan path env} {
   if {$path == "/"} {
      puts $chan [html_start "Guide data checker"]
      puts $chan [html_table_start "" "" "ALIGN=TOP"]
      puts $chan [tr "" [th "" "Guide data checker"]]
      puts $chan [tr "" [td [html_link "/gdchecker/datevschannel" "Date vs channel grid"]]]
      puts $chan [tr "" [td [html_link "/gdchecker/orphansps"     "Orphaned Season Passes"]]]
      puts -nonewline $chan [html_table_end]
      puts $chan [html_end]
   } elseif {$path == "/datevschannel"} {
      action_datevschannel $chan
   } elseif {$path == "/orphansps"} {
      action_orphansps $chan
   } else {
      puts $chan "Invalid path: $path"
   }
}
proc action_datevschannel {chan} {
   global channeltablestation channeltablenum
   global db
   
   puts $chan [html_start "Date vs channel grid"]
   
   set overallminday 00000
   set overallmaxday 99999
   
   foreach channum [lsort -real [array names channeltablenum]] {
      set stationid $channeltablenum($channum)
      set data $channeltablestation($stationid)
      set favourite [lindex $data 1]
      
      # get min and max dates for this channel
      set minday 99999
      set maxday 00000
      ForeachMfsFile fsid name type "/Schedule" "$stationid:" {
	 scan $name "%d:%d:" schedsid schedday
	 if {$minday > $schedday} {
	    set minday $schedday
	 }
	 if {$maxday < $schedday} {
	    set maxday $schedday
	 }
      }
      if {[expr $maxday - $minday] < 3} {
	 # exclude stations with less than three days data
	 continue
      }
      
      # compare with overall min and max
      if {$overallminday < $minday} {
	 set overallminday $minday
      }
      if {$overallmaxday > $maxday} {
	 set overallmaxday $maxday
      }
   }
   # overallminday is now the earliest date that they all have data (some
   # may have earlier data, but not all). overallmaxday is now the latest
   # date that they all have data (some may have later data, but not all).
   if {[expr $overallmaxday - $overallminday] >30} {
      set overallmaxday [expr $overallminday + 30]
   }
   puts $chan [html_table_start "" "" ""]
   puts $chan [tr "ALIGN=LEFT"   [th ""]    [th "F"] [th ""] [gdchecker_dateheadermonths $chan $overallminday $overallmaxday]]
   puts $chan [tr "ALIGN=CENTER" [th ""]    [th "a"] [th ""] [gdchecker_dateheaderdates $chan $overallminday $overallmaxday]]
   puts $chan [tr "ALIGN=CENTER" [th "Num"] [th "v"] [th "Callsign"] [gdchecker_dateheaderdays $chan $overallminday $overallmaxday]]
   
   foreach channum [lsort -real [array names channeltablenum]] {
      set stationfsid $channeltablenum($channum)
      set data $channeltablestation($stationfsid)
      set favourite [lindex $data 1]
      set callsign [lindex $data 2]
      if { $favourite == 1 } {
	 set favstr "*"
      } else {
	 set favstr ""
      }
      
      # now call all the work
      puts $chan [tr "ALIGN=CENTER" [td "ALIGN=LEFT" $channum] [td $favstr] [td "ALIGN=LEFT" [html_link "/channel/$stationfsid" $callsign]] [gdchecker_datelinks $chan $overallminday $overallmaxday $stationfsid]]
   }
   puts -nonewline $chan [html_table_end]
   
   # now the key table
   puts $chan "
"
   puts $chan [html_table_start "" "" "ALIGN=TOP"]
   puts $chan [tr "" [td ""] [td "ALIGN=left" "Select blobs above to jump to listings for each channel/day"]]
   set imagestr "#"
   puts $chan [tr "" [td "ALIGN=left" "#"] [td "Schedule ends short"]]
   puts $chan [tr "" [td "ALIGN=left" "#"] [td "Schedule starts late"]]
   puts $chan [tr "" [td "ALIGN=left" "#"] [td "Gaps in schedule data"]]
   puts $chan [tr "" [td "ALIGN=left" "#"] [td "placeholder"]]
   puts $chan [tr "" [td "ALIGN=left" "#"] [td "Normal listings"]]
   puts $chan [html_table_end]
   puts $chan [html_end]
}
proc gdchecker_dateheadermonths {chan minday maxday} {
   set minseconds [expr $minday * 86400]
   set maxseconds [expr $maxday * 86400]
   set outputstr  ""
   set monthcount 0
   set monthlast  ""
   
   # month, in header cells
   for {set i $minseconds} {$i <= $maxseconds} {incr i 86400} {
      set timestr [clock format $i -format "%b"]
      if {$timestr != $monthlast} {
	 if {$monthcount != 0} {
	    # print block of cells for previous month, reset count
	    append outputstr [th "colspan=$monthcount" $monthlast]
	    set monthcount 0
	 }
      }
      set monthlast $timestr
      incr monthcount
   }
   # print final block of cells
   append outputstr [th "colspan=$monthcount" $monthlast]
   
   return $outputstr
}
proc gdchecker_dateheaderdates {chan minday maxday} {
   set minseconds [expr $minday * 86400]
   set maxseconds [expr $maxday * 86400]
   set outputstr  ""
   
   # date, in a header cell
   for {set i $minseconds} {$i <= $maxseconds} {incr i 86400} {
      set timestr [clock format $i -format "%e"]
      append outputstr [th $timestr]
   }
   
   return $outputstr
}
proc gdchecker_dateheaderdays {chan minday maxday} {
   set minseconds [expr $minday * 86400]
   set maxseconds [expr $maxday * 86400]
   set outputstr  ""
   
   # weekday, in a header cell, only want 1st char
   for {set i $minseconds} {$i <= $maxseconds} {incr i 86400} {
      set timestr [clock format $i -format "%a"]
      set timestr [string index $timestr 0]
      append outputstr [th $timestr]
   }
   
   return $outputstr
}
proc gdchecker_datelinks {chan minday maxday stationfsid} {
   global channeltablestation
   global gd_expected
   
   # puts $chan "DEBUG: STATIONFSID $stationfsid
"
   if {[string index $stationfsid 0] == "/"} {
      set stationfsid [string range $stationfsid 1 end]
   }
   set firstslash [string first "/" $stationfsid]
   set seconds ""
   if { $firstslash != -1 } {
      set stationid [string range $stationfsid 0 [expr $firstslash - 1]]
      set seconds   [string range $stationfsid [expr $firstslash + 1] end]
   } else {
      set stationid $stationfsid
   }
   
   set minseconds [expr $minday * 86400]
   set maxseconds [expr $maxday * 86400]
   
   set outputstr   ""
   set gd_expected 0
   # output a line of cells
   for {set i $minseconds} {$i <= $maxseconds} {incr i 86400} {
      set timestr [clock format $i -format "%e"]
      
      # now check the station for known patterns
      if { [catch {set data $channeltablestation($stationid)}] != 1 } {
	 set blobstr [split [gdchecker_listings $chan $stationid $i] "|"]
	 # IE: regsub {,} $blobstr "\n" blobstr
	 set imagestr "#"
      } else {
	 # SEVERE error - unknown channel, couldn't read callsign
	 set imagestr "X"
      }
      append outputstr [td [html_link "/listings/$stationid/$i" $imagestr]]
   }
   return $outputstr
}
proc gdchecker_listings {chan stationid seconds} {
   global db
   global channeltablestation
   global channeltablenum
   
   set listingsoutputstr ""
   
   set day       [expr $seconds / 86400]
   set startsecs $seconds
   set endsecs   [expr $startsecs + 86400]
   
   ForeachMfsFileTrans fsid name type "/Schedule" "$stationid:$day:" 10 {
      append listingsoutputstr [gdchecker_listingsrow $chan $fsid $startsecs $endsecs]
   }
   
   if {$listingsoutputstr == ""} {
      return "blue|No guide data"
   } else {
      return $listingsoutputstr
   }
}
proc gdchecker_listingsrow {chan fsid startsecs endsecs} {
   global db
   global tzoffset
   global gd_expected
   set secs     0
   set duration 0
   set colour   "green"
   set info     ""
   set stationday [db $db openid $fsid]
   set showings   [dbobj $stationday get Showing]
   foreach showing $showings {
      set time [dbobj $showing get Time]
      set secs [expr [dbobj $showing get Date] * 86400 + $time + $tzoffset]
      if { $secs >= $startsecs && $secs < $endsecs } {
	 if {$gd_expected > 0 && $duration == 0 && $secs > $gd_expected && $secs > $startsecs} {
	    set timestr [clock format $secs -format "%H:%M"]
	    set info   "${info},Day starts late: $timestr"
	    set colour "orange"
	 }
	 if {$gd_expected > 0 && $duration > 0 && $secs != $gd_expected} {
	    set timestr [clock format $secs -format "%H:%M"]
	    set timeexp [clock format $gd_expected -format "%H:%M"]
	    set info   "${info},Gap in data: $timeexp-$timestr"
	    set colour "yellow"
	 }
	 set duration    [dbobj $showing get Duration]
	 set gd_expected [expr $secs + $duration]
      }
   }
   if { [expr $secs + $duration] < $endsecs } {
      set timestr [clock format [expr $secs + $duration] -format "%H:%M"]
      set info   "${info},Day ends short: $timestr"
      set colour "red"
   }
   
   if {[string index $info 0] == ","} {
      set info [string range $info 1 end]
   }
   return "$colour|$info"
}
# #############################################################
proc action_orphansps {chan} {
   global db
   global tzoffset
   global seasonpassdir
   
   puts $chan [html_start "Orphaned Season Pass episodes"]
   set priority 0
   set splist ""
   ForeachMfsFileTrans fsid name type $seasonpassdir "" 20 {
      set sp [db $db openid $fsid]
      set type [defaultval 1 [dbobj $sp get Type]]
      incr priority
      lappend splist $priority
      if { $type == 1 } {
	 set series [dbobj $sp get Series]
	 set spfsid [dbobj $sp fsid]
	 set seriesfsid [dbobj $series fsid]
	 set title [strim [dbobj $series get Title]]
	 lappend splist $seriesfsid $title
	 set station [dbobj $sp get Station]
	 set stationfsid [dbobj $station fsid]
	 set callsign [dbobj $station get CallSign]
	 lappend splist $stationfsid $callsign
      } elseif { $type == 2 } {
	 set station [dbobj $sp get Station]
	 set stationfsid [dbobj $station fsid]
	 set starttimelocal [defaultval 0 [dbobj $sp get StartTimeLocal]]
	 set starttime [expr $starttimelocal - $tzoffset]
	 set dowlocal [dbobj $sp get DayOfWeekLocal]
	 if { [llength $dowlocal] > 1 } {
	    set rectime ""
	    foreach dowl $dowlocal {
	       set tmprectime [get_nextmanualrectime $dowl $starttime]
	       if { $rectime == "" || $tmprectime < $rectime } {
		  set rectime $tmprectime
	       }
	    }
	 } else {
	    set rectime [get_nextmanualrectime $dowlocal $starttime]
	 }
	 set datestr [expr $rectime / 86400]
	 set schedlist [get_fsidbyprefix "/Schedule" "$stationfsid:$datestr:"]
	 if { [scan [lindex $schedlist 1] "%d:%d:%d:%d:" dummy dummy2 starttimesched duration] == 4 } {
	    regsub {^0+([1-9])} $starttimesched {\1} starttimesched
	    regsub {^0+([1-9])} $duration {\1} duration
	    if {$starttimesched > $starttime} {
	       set datestr [expr ($rectime / 86400) - 1]
	       set schedlist [get_fsidbyprefix "/Schedule" "$stationfsid:$datestr:"]
	       if { [scan [lindex $schedlist 1] "%d:%d:%d:%d:" dummy dummy2 starttimesched duration] != 4 } {
		  error "Invalid /Schedule format: '[lindex $schedlist 1]'"
	       }
	       regsub {^0+([1-9])} $starttimesched {\1} starttimesched
	       regsub {^0+([1-9])} $duration {\1} duration
	    }
	    if { $::dtivo } {
	       if {$starttimesched + $duration <= $starttime} {
		  set stoptimestr [format "%05d" [expr $starttimesched + $duration]]
		  set schedlist [get_fsidbyprefix "/Schedule" "$stationfsid:$datestr:$stoptimestr:"]
	       }
	    }
	 } else {
	    #error "Invalid /Schedule format: '[lindex $schedlist 1]'"
	 }
	 set callsign [dbobj $station get CallSign]
	 set spfsid [dbobj $sp fsid]
	 if { $schedlist != "" } {
	    set stationday [db $db openid [lindex $schedlist 0]]
	    set showings [dbobj $stationday get Showing]
	    set mshowing ""
	    foreach showing $showings {
	       set stime [dbobj $showing get Time]
	       set sdur [dbobj $showing get Duration]
	       if { $stime <= $starttime && $starttime < [expr $stime + $sdur] } {
		  set mshowing $showing
		  break
	       }
	    }
	    if { $mshowing != "" } {
	       set program [dbobj $mshowing get Program]
	       set series [dbobj $program get Series]
	       set seriesfsid [dbobj $series fsid]
	       set title [strim [dbobj $series get Title]]
	       set title [html_link "/series/$seriesfsid" $title]
	       lappend splist $seriesfsid $title
	    } else {
	       lappend splist 0 "Manual Recording"
	       # puts "Error finding manual recording"
	    }
	 } else {
	    lappend splist 0 "Manual Recording"
	    # puts "Error finding manual recording"
	 }
	 lappend splist $stationfsid $callsign
      } elseif { $type == 3 } {
	 #wishlist
	 set theme [dbobj $sp get Theme]
	 set spfsid [dbobj $sp fsid]
	 set themefsid [dbobj $theme fsid]
	 set title [strim [dbobj $theme get Name]]
	 lappend splist 0 $title 0 ""
      }
   }
   
   # do this once here, rather than once per search
   update_sp_cache
   
   # now print the table from the list, and do searches against each title
   puts $chan [html_table_start "" "" ""]
   puts $chan [tr "ALIGN=CENTER" [th "Pri"] [th "Title"] [th "Channel"] [th "Matches found from title search"]]
   foreach {priority seriesid title stationfsid callsign} $splist {
      set rowstr ""
      append rowstr [td $priority]
      if {$stationfsid == 0} {
	 # wishlist
	 append rowstr [td $title] [td "(wishlist)"]
      } elseif {$seriesid == 0} {
	 # some manual SPs
	 append rowstr [td $title]
	 append rowstr [td [html_link "/channel/$stationfsid" $callsign]]
      } else {
	 # normal SPs and manual SPs
	 append rowstr [td [html_link "/series/$seriesid" $title]]
	 append rowstr [td [html_link "/channel/$stationfsid" $callsign]]
      }
      # now search for this title, and see how many results we get
      # more than one result could be bad! let the user decide
      if {$seriesid == 0} {
	 # don't do the search for wishlists
	 append rowstr [td "(search not performed)"]
      } else {
	 append rowstr [td [action_spsearch $title]]
      }
      puts $chan [tr "" $rowstr]
   }
   puts $chan [html_table_end]
   puts $chan [html_end]
}
proc action_spsearch {title} {
   global cache_sp_key
   global images
   
   # Now output search results. This was a search action
   regsub -nocase {^(a|the) } $title {} modq
   regsub -all {\$} $modq {} modq
   regsub -all {\*} $modq {} modq
   regsub -all {\,} $modq {} modq
   
   regsub -all {[^A-Za-z0-9\*\", 	]} $modq {} modq
   regsub -all {\"} $modq {} modq
   regsub "\t$" $modq {} dispq
   if {[string length $dispq] == 0} {
      return [h2 "Error, empty search query"]
   }
   if {[string length $dispq] < 2} {
      return [h2 "Error, require at least 2 characters in query"]
   }
   set searchResultsTitle [htmlEncode $dispq]
   #set searchResultsTitle "Search Results for \"$searchResultsTitle\""
   
   set fsids [get_textindexsearch $modq 0]
   
   if {[lindex $fsids 0] == -1} {
      return "No matches found in the current Guide Data"
   } elseif {[lindex $fsids 0] == -2} {
      return "Error running search [lrange $fsids 1 end]"
   } else {
      set spsearch_output ""
      
      # update_sp_cache
      foreach item [lrange $fsids 1 end] {
	 set seriestitle [lindex $item 0]
	 set fsid [lindex $item 1]
	 set fsid [expr 0x$fsid]
	 set genres [lindex $item 2]
	 regsub {^(.*), (A|The)$} $seriestitle {\2 \1} seriestitle
	 set slists [get_programshowings $fsid 1]
	 if {$slists != ""} {
	    set index [lsearch $cache_sp_key "$fsid|*"]
	    if {$index > -1} {
	       set imagestr [img "alt=\"\"" [lindex $images 11]]
	    } else {
	       set imagestr "X"
	    }
	    set link "/series/$fsid"
	    if {$spsearch_output != ""} {
	       set spsearch_output "$spsearch_output
$imagestr [html_link $link [htmlEncode $seriestitle]]"
	    } else {
	       set spsearch_output "$imagestr [html_link $link [htmlEncode $seriestitle]]"
	    }
	 }
      }
      if {$spsearch_output != ""} {
	 return "$spsearch_output"
      } else {
	 return "No matches found in the current Guide Data"
      }
   }
}
register_module "gdchecker" "GDchecker" "Check Guide Data"