=begin
 ******************************************************************************
 * Copyright 1990-2009 MBARI
 * MBARI Proprietary Information. All rights reserved.
 ******************************************************************************
 * Summary  : Base class for Benthic Rover data and log files
 * Filename : datalog.rb
 * Author   : Henthorn
 * Project  : Benthic Rover
 * Version  : 1
 * Created  : Oct 2008
 * Modified :
 ******************************************************************************
=end

##
# Refer to Rover-code home directory as base
#
require "#{ENV['ROVER_HOME']}/utils/rover_environment"
#

require 'utils/misc'

##########
# DataLog class provides base class functionality and helper methods for
# creating and writing Rover data and log files.
#
class DataLog

  attr_accessor :file, :name, :need_heading

  ##
  # Types:        string      boolean
  # Units:         none       true=do not buffer data, flush almost immediately
  #
  def initialize(p_filename, p_sync=true) # Can be full path or relative path
    @need_heading = true unless File.exist?(p_filename)

    @file = File.new(p_filename, "a+")    # Do not overwrite existing data
    @file.sync = p_sync
    @name = File.expand_path(p_filename)
  end

  ##
  # Variable-arg write method (zero or more).
  # Write the given parameters separated by commas.
  # Pre-pend timestamp to line of data.
  # Returns the number of bytes written to the file.
  # Returns nil if the data file is not kosher.
  #
  def write(*p_par)
    return nil unless @file

    # Write the timestamp followed by the parameters if there are any.
    # Track the number of bytes written.
    #
    li_total_bytes = 0
    li_total_bytes += @file.write(DataLog.timestamp())
    sleep(0)
    li_total_bytes += @file.write(",#{p_par.join(',')}") if (p_par.length > 0)
    li_total_bytes += @file.write("\n")
    @file.flush

    return li_total_bytes
  end

  ##
  # Callable test function.
  # Purpose is to test functionality and run every line of code in
  # the class.
  #
  def test_me
    puts (write("Single,arg,already,comma,separated"))
    puts (write("File name is", @name, "Size is", File.size(@name)))
    puts (write(nil, "1st param", "is nil. Next", "line has no params"))
    puts (write())
  end


  ############    Class  members  and  methods    ############

  ##
  # Return the top-level log directory
  #
  def DataLog.log_home
    if (lh = ENV['ROVER_LOGS'])
      lh += "/" unless (lh[-1..-1]=="/")
      return lh
    else
      return "#{ENV['ROVER_HOME']}/logs/"
    end
  end

  def DataLog.backup_home
    return "/mnt/usb0/"
  end

  @@deployment_data_backup_home = DataLog.backup_home + "/testlogs/"
  @@deployment_data_home = DataLog.log_home + "/testlogs/"
  @@plan_data_home = @@deployment_data_home
  @@behavior_data_home = @@plan_data_home
  @@system_logs = @@deployment_data_home
  @@acm_logs = @@deployment_data_home

  ##
  # datestamp - return standard datestamp string for naming log directories
  #
  def DataLog.datestamp
    today = Time.now
    year = today.year.to_s
    if (today.month > 9)  # month number
      month = "." + today.month.to_s
    else
      month = ".0" + today.month.to_s
    end

    if (today.day > 9)  # day number
      day = "." + today.day.to_s
    else
      day = ".0" + today.day.to_s
    end
    stamp = year + month + day
  end


  ##
  # Create a new deployment log top-level directory under the
  # standard Rover log directory returned by DataLog.log_home.
  # Point to the that new directory.
  #
  def DataLog.deployment_data_home=(deploy_name)
    # Determine a unique deployment log directory name for this deployment and date
    #
    prefix  = "" + DataLog.log_home
    dname   = "dep-" + DataLog.datestamp + "-"
    postfix = "-" + deploy_name

    # Find next sequence number for today
    #
    mn_zero = "0"
    logdir = Dir.new(DataLog.log_home)
    for mn in 1..1000 do
      mn_zero = "" if (mn > 9)
      test_name = dname + mn_zero + mn.to_s

      # Not unique if dname + mn number has been used already
      #
      unique = true
      logdir.rewind
      logdir.each { |d|
        if (d.index(test_name) == 0)
          unique = false
          break
        end
      }
      break if (unique) # Unique name
    end

    # Append postfix for deployment dir name
    #
    dir_name = prefix + dname + mn_zero + mn.to_s + postfix
    dir = Dir.mkdir(dir_name)
    @@deployment_data_home = dir_name + "/"
    @@plan_data_home = @@deployment_data_home
    @@behavior_data_home = @@plan_data_home

    # Do the same for the deployment backup dir name
    #
    prefix = DataLog.backup_home
    dir_name = prefix + dname + mn_zero + mn.to_s + postfix
    #dir = Dir.mkdir(dir_name)
    @@deployment_data_backup_home = dir_name + "/"

    ##
    # Create deployment data directories
    #
    @@system_logs = @@deployment_data_home + "data-01-system_logs/"
    @@acm_logs = @@deployment_data_home + "data-02-currents/"
    Dir.mkdir(@@system_logs)
    #Dir.mkdir(@@acm_logs)

    begin
      # Point the logs/latest symlink to the new deployment dir
      #
      symname = DataLog.log_home + "latest"
      File.unlink(symname) if File.exist?(symname)
      File.symlink(@@deployment_data_home, symname)

      # Point the backup symlink to the new deployment backup dir
      #
      symname = DataLog.deployment_data_home + "backup"
      #File.unlink(symname) if File.exist?(symname)
      #File.symlink(@@deployment_data_backup_home, symname)

    rescue Errno::EPERM => e
      # Running on a FAT32 system? Keep going
    end
  end

  def DataLog.deployment_data_home
    return @@deployment_data_home
  end

  def DataLog.deployment_data_backup_home
    return @@deployment_data_backup_home
  end

  #####
  # Normalize directory names by removing excess '/' characters
  # and ensuring that the last character is '/'
  #
  def DataLog.normalize_dir_name(dirname)
    return nil unless (dirname)

    # Strip whitespace at the ends and remove excess '/'
    #
    dirname = dirname.strip().gsub(/\/+/, '/')

    # Append a '/' at the end if needed
    #
    dirname += "/" unless (dirname[-1..-1]=="/")
    return dirname
  end

  #####
  # Use this method to set the deployment data home to a directory
  # that already exists. Used when resuming a hibernating deployment.
  # Returns true is the directory exists. Otherwise false.
  #
  def DataLog.set_deployment_data_home=(dir_name)
    return false unless (dir_name)

    dir_name = DataLog.normalize_dir_name(dir_name)
    if (File.exists?(dir_name))
      # Append a slash unless one is already there
      @@deployment_data_home = dir_name
      @@deployment_data_backup_home = @@deployment_data_home + "backup/"
      @@system_logs = @@deployment_data_home + "data-01-system_logs/"
      @@acm_logs = @@deployment_data_home + "data-02-currents/"

      @@plan_data_home = @@deployment_data_home
      @@behavior_data_home = @@plan_data_home
      return true
    end
    return false
  end
  #####

  #####
  # Use this method to set the plan data home to a directory
  # that already exists. Used when resuming a hibernating plan.
  # Returns true is the directory exists. Otherwise false.
  #
  def DataLog.set_plan_data_home=(dir_name)
    return false unless (dir_name)

    dir_name = DataLog.normalize_dir_name(dir_name)
    if (File.exists?(dir_name))
      @@plan_data_home = dir_name
      @@behavior_data_home = @@plan_data_home
      return true
    end
    return false
  end
  #####

  #####
  # Use this method to set the behavior data home to a directory
  # that already exists. Used when resuming a hibernating behavior.
  # Returns true is the directory exists. Otherwise false.
  #
  def DataLog.set_behavior_data_home=(dir_name)
    return false unless (dir_name)

    dir_name = DataLog.normalize_dir_name(dir_name)
    if (File.exists?(dir_name))
      @@behavior_data_home = dir_name
      return true
    end
    return false
  end
  #####

  def DataLog.system_logs
    return @@system_logs
  end

  def DataLog.acm_logs
    # return @@acm_logs
    return @@system_logs
  end


  #####
  # Create a new plan-level directory under the
  # current deployment log directory, and
  # point to the that new directory.
  #
  def DataLog.plan_data_home=(plan_name)
    # Determine a unique deployment log directory name for this plan and date
    #
    prefix  = "" + DataLog.deployment_data_home + "plan-"
    postfix = "-" + plan_name + "-" + DataLog.datestamp

    # Find next sequence number
    #
    mn_zero = "0"
    for mn in 1..1000 do
      mn_zero = "" if (mn > 9)
      dir_name = prefix + mn_zero + mn.to_s + postfix
      break unless (File.exist?(dir_name)) # Unique name
    end
    dir = Dir.mkdir(dir_name)
    @@plan_data_home = dir_name + "/"
    @@behavior_data_home = @@plan_data_home
  end

  def DataLog.plan_data_home
    return @@plan_data_home
  end


  #####
  # Create a new behavior-level directory under the current plan data
  # directory, and point to that new directory.
  # Note: not all behaviors need a data directory (e.g., MakeHeading) 
  #
  def DataLog.behavior_data_home=(p_data_dir_name)
    # Determine a unique deployment log directory name for this plan and date
    #
    #prefix  = "" + DataLog.plan_data_home + p_behavior_name + "-"
    prefix  = "" + DataLog.plan_data_home + "data-"

    dir_name = prefix + p_data_dir_name
    dir = Dir.mkdir(dir_name)
    @@behavior_data_home = dir_name + "/"
  end

  def DataLog.behavior_data_home
    return @@behavior_data_home
  end


  ##
  # timestamp()
  # Return string representation of the current system time in ISO format
  # Precision is hundredths of a second.
  #
  def DataLog.timestamp()
    t = Time.now
    return t.gmtime.strftime("%Y-%m-%dT%H:%M:%S.") + t.usec.to_s[0..1]
  end

  ##
  # backup_plan(directory)
  # Create a backup copy of current plan data or the given directory to the
  # current deployment backup directory.
  # If not directory is specified, copy the latest plan data dir
  #
  def DataLog.backup_plan(directory=nil)
    directory = DataLog.plan_data_home unless (directory)
    backup_cmd = "cp -a #{directory} #{DataLog.deployment_data_backup_home}/. "
    response = backup_cmd + ": " + `#{backup_cmd}`
    response
  end

  def DataLog.backup_logs()
    sysl = self.system_logs + "/syslog.csv"
    backup_cmd = "tail -1000 #{sysl} > #{self.deployment_data_backup_home}/syslog.csv"
    response = backup_cmd + ": " + `#{backup_cmd}`
    response
  end

end

##########
# Syslog class provides system tracing capability.
# Strives to always have a syslog open.
# TODO: Add tracing levels? i.e., low, med, hi, etc.
#
class Syslog
  include Singleton

  attr_accessor :dir, :filename, :file

  ##
  # Singleton instance created referencing default file
  #
  def initialize
    @dir = "" + DataLog.log_home + "testlogs/"
    @filename = "syslog.csv"
    @file = DataLog.new(self.filename, "a+")
  end

  ##
  # Changing the directory will create a new syslog file.
  # The directory must already exist.
  #
  def dir=(p_dirname)
    return nil unless (File.exists?(p_dirname))
    return nil unless (File.stat(p_dirname).directory?)

    # Directory exists so open the syslog file
    @dir = p_dirname
    @file = DataLog.new(self.filename, "a+")
    return true
  end

  ##
  # Returns the path name, not just "syslog.csv"
  #
  def filename
    return @dir + @filename
  end

end

class Console < Syslog
  def initialize
    super
    @filename = "console.csv"
    @file = DataLog.new(self.filename, "a+")
  end
end

##########
# Include this module to gain access to nice syslog and console functions
#
module SyslogWriter
  def syslog(*p_list)
    Syslog.instance.file.write(self.class.name, p_list)
  end

  def console(*p_list)
    puts(*p_list.join('\t'))
    Console.instance.file.write(self.class.name, p_list)
  end
end

##
# Standalone unit test (recommended)
#
if __FILE__ == $0
  # Test code here
  dl = DataLog.new("/tmp/DataLog_test.csv")
  dl.test_me()

  DataLog.deployment_home = "test_home"
  DataLog.plan_home = "plan"

  # Syslog tests
  include SyslogWriter
  syslog("This should show up", "in the standard", "syslog file")
  # This dirname call should fail
  syslog("1st", "Create the dir") unless Syslog.instance.dir="/tmp/Syslog_test"
  Dir.mkdir("/tmp/Syslog_test") unless File.exists?("/tmp/Syslog_test")
  # Now we're cookin'
  syslog("1st", "Create the dir") unless Syslog.instance.dir="/tmp/Syslog_test"
  syslog("I", "Created the dir 1st") if Syslog.instance.dir="/tmp/Syslog_test"

  puts("Done")
end
