#!/bin/tclsh
#Version 1.5
#param 0 = Breitengrad (nördlich)
#param 1 = Längengrad (östlich)
#param 2 = künstlicher Horizont
#param 3 = PLZ

#only run once, check if locking port 60 is opened
if {[catch {socket -server unknown -myaddr 127.0.0.1 60} locksock]} then {
  exit 0
}

source /www/addons/lcd_msg/daemonize.tcl

load tclrega.so

proc showTime x {
  if {$x && $x != 2000000000} then {
    clock format $x -format "%H:%M:%S"
  } else {
    return "--:--:--"
  }
}

proc log x {
  puts $::logfile "[showTime [clock seconds]]: $x"
  flush $::logfile
}

#define constants for the list access to make review easier
set NIGHT 0
set SR_ASTRONOMICAL 1
set SR_NAUTICAL 2
set SR_CIVIL 3
set SR_SUNRISE 4
set SR_INDOOR 5
set SR_WEATHER 6
set SS_WEATHER 7
set SS_INDOOR 8
set SS_SUNRISE 9
set SS_CIVIL 10
set SS_NAUTICAL 11
set SS_ASTRONOMICAL 12

proc getTimeDiff {dayofyear} {
  return [expr {-0.171*sin(0.0337 * $dayofyear + 0.465) - 0.1299*sin(0.01787 * $dayofyear - 0.168)}]
}

proc calcTwilightTimes {latitude longitude timezone dayofyear horizon sunrisevar sunsetvar} {
  upvar $sunrisevar sunrise
  upvar $sunsetvar sunset

  if {[catch {
    set timediff [getTimeDiff $dayofyear]
    set declination [expr {0.4095 * sin(0.016906 * ($dayofyear - 80.086) ) }]
    set suntime [expr {12*acos((sin($horizon/57.29578) - sin($latitude/57.29578)*sin($declination)) / (cos($latitude/57.29578)*cos($declination)))/3.141592}]
    set sunrise [expr {[clock scan "0"] + round((12 - $timediff - $suntime - $longitude/15.0 + $timezone)*3600)}]
    set sunset  [expr {[clock scan "0"] + round((12 - $timediff + $suntime - $longitude/15.0 + $timezone)*3600)}]
  }] } then {
    set sunrise 0
    set sunset 2000000000
  }
}

proc getTwilightHorizon {logfile plz base_horizon} {

  set url http://www.google.com/ig/api?weather=$plz-Germany&hl=de
  set twilightHorizon [expr {$base_horizon + 5.0}]
  catch {
    exec /usr/bin/wget -q -O /tmp/twilightweather.xml $url
    set f [open "/tmp/twilightweather.xml"]
    set a [read $f]
    close $f
    regexp "<current_conditions>(.*)</current_conditions>" $a dummy current
    foreach tag [split $current >] {
      regexp {<([^=>]*) data="([^=>"]*)"/} $tag dummy key value
        #" for editor
      set aCurrent($key) "$value"
    }
    ::log "current condition is \"$aCurrent(condition)\""
    file delete /tmp/twilightweather.xml
    switch $aCurrent(condition) {
      "Klar"              {set twilightHorizon [expr {$base_horizon + 0.2}]}
      "Meist sonnig"      {set twilightHorizon [expr {$base_horizon + 1.5}]}
      "Teils sonnig"      {set twilightHorizon [expr {$base_horizon + 3.0}]}
    }
  }
  ::log "twilight horizon set to $twilightHorizon"
  return $twilightHorizon
}

proc getDayOfYear {timestamp} {
  set dayofyear [string trimleft [clock format $timestamp -format "%j"] "0"]
  #subtract the fractional part to the next leap-year february 29th from the current day of the year
  set dayofyear [expr {$dayofyear - ([clock format $timestamp -format "%Y"]%4)/4.0 - ($dayofyear)/365.25/4.0}]
  return $dayofyear
}

proc waitToTime {logfile destTime} {
  set waittime [expr {1000*($destTime - [clock seconds])}]
  if {$waittime > 0} then {
    log "waiting [expr $waittime/1000] s until [clock format $destTime]"
    #after is off of several seconds (exactly 1/1000?) after long waits, so wait 1% shorter and then wait again
    after [expr { 990*($destTime - [clock seconds])}]
    after [expr {1000*($destTime - [clock seconds])}]
  } else { 
    log "not waiting [expr $waittime/1000] s until [clock format $destTime]" 
  } 
}

proc displayValues {} {
  uplevel {
    set display "s. folgende Tabelle vom [clock format [lindex $twilight_times 6] -format "%d.%m."]"
    append display "</div><br/><table border=\"1\" bordercolor=\"#000000\" style=\"background-color:#5d6373\" width=\"100%\">\r\n"
    append display "<tr><td><b>Dämmerung</b></td><td><b>Morgens</b></td><td><b>Abends</b></td></tr>\r\n"
    for {set j 1} {$j <= 6} {incr j} {
      
      set insert "[lindex $twilight_descr $j]:"
      if {$j == $i || $j == 12-$i} then {set insert "<b>$insert</b>"}
      append display "<tr><td>$insert</td>"
      
      set insert "[showTime [lindex $twilight_times [expr $j-1]]]"
      if {$j == $i} then {set insert "<b>$insert</b>"}
      if {$j == 6 && !$srw_set} then {set insert "<i>$insert</i>"}
      append display "<td>$insert</td>"
      
      set insert "[showTime [lindex $twilight_times [expr 12-$j]]]"
      if {$j == 13-$i} then {set insert "<b>$insert</b>"}
      if {$j == 6 && !$ssw_set} then {set insert "<i>$insert</i>"}
      append display "<td>$insert</td></tr>\r\n"
      #log "updating twilight time table: [showTime [lindex $twilight_times [expr $j-1]]] [showTime [lindex $twilight_times [expr 12-$j]]] [string map {: { }} [lindex $twilight_descr [expr $j-1]]]"
    }
    append display "</table></td>"
    rega_script "dom.GetObject('Dämmerungszeiten').State('$display');"
  }
}

#
# Main execution
#

set latitude [lindex $argv 0]
set longitude [lindex $argv 1]
set indoor_horizon [lindex $argv 2]
set plz [lindex $argv 3]

#some vars we need
set horizon_values [list 0 -18 -12 -6 [expr {- 50.0 / 60.0}] $indoor_horizon [expr {$indoor_horizon + 5.1}]]
set twilight_names [list night astronomical nautical civil standard indoor weather]
set twilight_descr [list Nacht Astronom. Nautisch Bürgerlich Standard Indoor Wetterabh.]

while {1} {
  set timezone [expr {([clock scan "0 UTC"] - [clock scan "0"]) / 3600.0}]
  set now [clock seconds]
  set twilight_midnight [expr {[clock scan "0"] + round((0 - [getTimeDiff [getDayOfYear $now]] - $longitude/15.0 + $timezone)*3600)}]

  #get the day of the year as numeric value based on twilight midnight (time between midnight and twilight midnight belongs to day before)
  set yesterday_offset 0
  if {$now < $twilight_midnight} then {
    #use time table from day before
    set yesterday_offset 86400
  }
  set dayofyear [getDayOfYear [expr $now-$yesterday_offset]]
  
  catch {file delete "/tmp/twilight.[expr int($dayofyear)-3].log"}
  set logfile [open "/tmp/twilight.[expr int($dayofyear)].log" w]
  log "args: $argv"
  if {$now < $twilight_midnight} then {log "twilight midnight not reached, using yesterdays time table"}
  log "timezone: $timezone, twilight midnight: [showTime $twilight_midnight], dayofyear: $dayofyear"
  
  set twilight_times ""
  for {set i 1} {$i <= 6} {incr i} {
    calcTwilightTimes $latitude $longitude $timezone $dayofyear [lindex $horizon_values $i] sunrise_time sunset_time
    if {$sunrise_time == 0} then {
      log "no [lindex $twilight_names $i] twilight here and now"
    } elseif {$i != 6} then {
      log "calculated twilight times: sunrise [clock format $sunrise_time -format "%d., %H:%M:%S"], sunset [clock format $sunset_time -format "%d., %H:%M:%S"] for [lindex $twilight_names $i] ($i)"
    }
    #do not append weather sunrise
    if {$i != 6} then {
      lappend twilight_times [expr $sunrise_time-$yesterday_offset]
    }
    #append indoor sunrise again to replace weather sunrise
    if {$i == 5} then {
      lappend twilight_times [expr $sunrise_time-$yesterday_offset+1]
    } 
    #always append sunset
    lappend twilight_times [expr $sunset_time-$yesterday_offset]
  }
  set twilight_times [lsort $twilight_times]
  log "calculated twilight times: sunrise [clock format [lindex $twilight_times 5] -format "%d., %H:%M:%S"], sunset [clock format [lindex $twilight_times 6] -format "%d., %H:%M:%S"] for [lindex $twilight_names 6] (6)"
  set srw_set 0
  set ssw_set 0
  displayValues

  #in the following calculation, assume that the weather sunrise and sunset are always subsequently on the current dayofyear    
  for {set i 0} {$i < 12} {incr i} {
    set next_time [lindex $twilight_times $i]
    
    if {$i == 5 && [clock seconds] < [lindex $twilight_times 6]} then {
      #sunrise weather
      log "retrieving weather condition for sunrise"
      calcTwilightTimes $latitude $longitude $timezone $dayofyear [getTwilightHorizon $logfile $plz $indoor_horizon] sunrise_weather dummy
      set next_time $sunrise_weather
      set srw_set 1
      set twilight_times [lreplace $twilight_times 5 5 $sunrise_weather]
      displayValues
      log "weather sunrise at [showTime $next_time]"
    }
    if {[clock seconds] < $next_time && $next_time != 2000000000} then {
      log "current twilight state $i ([lindex $twilight_descr [expr {6-abs($i-6)}]]), sunlight state [expr {6-abs($i-6)}], next at [showTime $next_time]"
      rega_script "dom.GetObject('Dämmerung').State('$i');"
      rega_script "dom.GetObject('Tageslicht').State('[expr {6-abs($i-6)}]');"
      displayValues
      waitToTime $logfile $next_time
    }
    if {$i == 6 && [clock seconds] < [lindex $twilight_times 7]} then {
      #sunset weather
      log "retrieving weather condition for sunset"
      calcTwilightTimes $latitude $longitude $timezone $dayofyear [getTwilightHorizon $logfile $plz $indoor_horizon] dummy sunset_weather
      set next_time $sunset_weather
      set ssw_set 1
      set twilight_times [lreplace $twilight_times 6 6 $sunset_weather]
      displayValues 
      log "weather sunset at [showTime $next_time]"
      waitToTime $logfile $next_time
    }
  }
  rega_script "dom.GetObject('Dämmerung').State('12');"
  rega_script "dom.GetObject('Tageslicht').State('0');"
  log "twilight cycle completed for day of year $dayofyear, waiting until twilight midnight for next run"
  waitToTime $logfile [expr {$twilight_midnight + 86400 - $yesterday_offset}]
  close $logfile
}
      
if {[file exists /var/run/twilight.tcl.pid]} then {
  catch {
    set f [open /var/run/twilight.tcl.pid]
    set filepid [read $f]
    close $f
    if {[pid] == $filepid} then {
      file delete /var/run/twilight.tcl.pid
    }
  }
}
close $locksock
