#!/usr/bin/env ruby

# Copyright 2010, 2011 Luther Thompson

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Version 0.2.0

$VERBOSE = true

require 'csv'

# Stores a running total for all trades of a currency. Can report high, low, and
# average.
class RunningTotal

  # Stores data for an individual trade
  class BTCPrice

    attr_reader :price

    # Arguments are the price and time of the trade
    def initialize price, time
      @price = price
      @time = time
    end

    def to_s
      priceString = sprintf '%-7g', @price
      timeString = @time.strftime '%Y-%_m-%e %a %k:%M'
      "#{priceString} #{timeString}"
    end

  end

  #Starts off empty
  def initialize
    @low = nil
    @high = nil
    @sum = 0
    @quantity = 0
  end

  # Processes a new trade.
  # row: A properly formatted CSV::Row
  def add row
    # Since bitcoin is destined to become the world's standard currency, we
    # price everything in BTC.
    priceData = BTCPrice.new 1 / row[:price], Time.at(row[:datetime])

    if @low == nil or priceData.price < @low.price
      @low = priceData
    end
    if @high == nil or priceData.price > @high.price
      @high = priceData
    end

    # Used for calculating the average. As written, it gives a higher weight to
    # when BTC are low. If I convert the quantity from BTC to the commodity, it
    # gives a higher weight to when the commodity is low.
    @sum += priceData.price * row[:quantity]
    @quantity += row[:quantity]
  end

  def to_s
    average = @sum / @quantity

    result =          "High:    #@high\n"
    result <<         "Low:     #@low\n"
    result << sprintf("Average: %g\n", average)
  end

end

argc = ARGV.length
if argc == 0
  # No time given. Use entire history.
  start = Time.at 0
  finish = Time.new
else
  ARGV.collect! { |arg|
    arg.to_i
  }
  # FIXME: Check for ArgumentError
  start = Time.new *ARGV
  year = ARGV[0]
  if argc == 1
    # Year
    finish = Time.new year + 1
  else
    # Number of seconds between start and finish
    period = 1
    if argc == 2
      # Month
      LeapYearMonthDays = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
      CommonYearMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
      month = ARGV[1]
      period = if year % 4 == 0 and year % 100 != 0 or year % 400 == 0
                 LeapYearMonthDays[month - 1]
               else
                 CommonYearMonthDays[month - 1]
               end
    end
    if argc <= 3
      # Day
      period *= 24
    end
    if argc <= 4
      # Hour
      period *= 60
    end
    if argc <= 5
      # Minute
      period *= 60
    end
    finish = start + period
  end
end

totals = Hash.new { |hash, key|
  hash[key] = RunningTotal.new
}

csvOptions = {
  headers: true,
  converters: :numeric,
  header_converters: :symbol
}

CSV.foreach('trades.csv', csvOptions) { |row|
  # Assumptions about the source data:
  # Reverse chronological order.
  # 'quantity' is the amount traded, in BTC.

  time = Time.at row[:datetime]
  if time < start
    break
  elsif time < finish
    key = row[:currency][-3..-1]
    totals[key].add row
  end
}

totals.keys.sort.each { |name|
  puts name
  puts totals[name]
  puts
}
