#!/usr/bin/env ruby

require 'gps_pvt'
require 'uri'

# runnable quick example to solve PVT by using RINEX NAV/OBS or u-blox ubx

$stderr.puts <<__STRING__
Usage: #{__FILE__} GPS_file1 GPS_file2 ...
As GPS_file, rinex_nav(*.YYn, *.YYh, *.YYq, *.YYg), rinex_obs(*.YYo), ubx(*.ubx), SP3(*.sp3), and ANTEX(*.atx) format are currently supported.
(YY = last two digit of year)
File format is automatically determined based on its extention described in above parentheses.
If you want to specify its format manually, command options like --rinex_nav=file_name are available.
Other than --rinex_nav, --rinex_obs, -rinex_clk, --ubx, --sp3 or --antex are supported. 
Supported RINEX versions are 2 and 3.
A file having additional ".gz" or ".Z" extension is recognized as a compressed file.
Major URL such as http(s)://... or ftp://..., and serial port (COMn for Windows, /dev/tty* for *NIX) is acceptable as an input file name.
__STRING__

options = []
misc_options = {}

# check options and file format
files = ARGV.collect{|arg|
  next [arg, nil] unless arg =~ /^--([^=]+)=?/
  k, v = [$1.downcase.to_sym, $']
  next [v, k] if [:rinex_nav, :rinex_obs, :ubx, :sp3, :antex, :rinex_clk].include?(k) # file type
  options << [$1.to_sym, $']
  nil
}.compact

options.reject!{|opt|
  case opt[0]
  when :start_time, :end_time
    require 'time'
    gpst_type = GPS_PVT::GPS::Time
    t = nil
    if opt[1] =~ /^(?:(\d+):)??(\d+(?:\.\d*)?)$/ then
      t = [$1 && $1.to_i, $2.to_f]
      t = gpst_type::new(*t) if t[0]
    elsif t = (Time::parse(opt[1]) rescue nil) then
      # leap second handling in Ruby Time is system dependent, thus 
      #t = gpst_type::new(0, t - Time::parse("1980-01-06 00:00:00 +0000"))
      # is inappropriate.
      subsec = t.subsec.to_f
      t = gpst_type::new(t.to_a[0..5].reverse)
      t += (subsec + gpst_type::guess_leap_seconds(t))
    else
      raise "Unknown time format: #{opt[1]}"
    end
    case t
    when gpst_type
      $stderr.puts(
          "#{opt[0]}: %d week %f (a.k.a %04d/%02d/%02d %02d:%02d:%02.1f)" \
            %(t.to_a + t.utc))
    when Array
      $stderr.puts("#{opt[0]}: #{t[0] || '(current)'} week #{t[1]}")
    end
    misc_options[opt[0]] = t
    true
  when :online_ephemeris
    misc_options[opt[0]] = opt[1]
    true
  else
    false
  end
}

# Check file existence and extension
files.collect!{|fname, ftype|
  ftype ||= case fname
  when /\.\d{2}[nhqg](?:\.gz)?$/; :rinex_nav
  when /\.\d{2}o(?:\.gz)?$/; :rinex_obs
  when /\.ubx$/; :ubx
  when /\.sp3(?:\.Z)?$/; :sp3
  when /\.atx(?:\.Z)?$/; :antex
  when /\.clk$/; :rinex_clk
  else
    raise "Format cannot be guessed, use --(format, ex. rinex_nav)=#{fname}"
  end
  fname = proc{
    next fname if File::exist?(fname)
    next fname if ((fname =~ Serial::SPEC) rescue false)
    if uri = URI::parse(fname) and !uri.instance_of?(URI::Generic) then
      next uri
    end
    raise "File not found: #{fname}"
  }.call
  [fname, ftype]
}

rcv = GPS_PVT::Receiver::new(options)

proc{|src|
  rcv.attach_online_ephemeris(src) if src
}.call(misc_options[:online_ephemeris])

proc{
  run_orig = rcv.method(:run)
  t_start, t_end = [nil, nil]
  tasks = []
  task = proc{|meas, t_meas, *args|
    t_start, t_end = [:start_time, :end_time].collect{|k|
      res = misc_options[k]
      res.kind_of?(Array) \
          ? GPS_PVT::GPS::Time::new(t_meas.week, res[1]) \
          : res
    }
    task = tasks.shift
    task.call(*([meas, t_meas] + args))
  }
  tasks << proc{|meas, t_meas, *args|
    next nil if t_start && (t_start > t_meas)
    task = tasks.shift
    task.call(*([meas, t_meas] + args))
  }
  tasks << proc{|meas, t_meas, *args|
    next nil if t_end && (t_end < t_meas)
    run_orig.call(*([meas, t_meas] + args))
  }
  rcv.define_singleton_method(:run){|*args|
    task.call(*args)
  }
}.call if [:start_time, :end_time].any?{|k| misc_options[k]}

puts rcv.header

# parse RINEX NAV
files.each{|fname, ftype|
  case ftype
  when :rinex_nav; rcv.parse_rinex_nav(fname)
  when :sp3; rcv.attach_sp3(fname)
  when :antex; rcv.attach_antex(fname)
  when :rinex_clk; rcv.attach_rinex_clk(fname)
  end
}

# other files
files.each{|fname, ftype|
  case ftype
  when :ubx; rcv.parse_ubx(fname)
  when :rinex_obs; rcv.parse_rinex_obs(fname)
  end
}