#!/usr/bin/perl -w
#
# FILE:
# gnc-prices
#
# FUNCTION:
# go out to the net, fetch prices for any securities,
# stocks/mutual funds in a GnuCash portfolio.
#
# HISTORY:
# Created by Linas Vepstas January 1999
# Copyright (c) 1999-2000 Linas Vepstas

# hack alert -- this pathname should *NOT* be hard-coded.
use lib '/usr/lib/gnucash';
use Quote;
use gnucash;

# --------------------------------------------------
# @account_list = &account_flatlist ($account_group);
# This rouine accepts a pointer to a group, returns 
# a flat list of all of the children in the group.

sub account_flatlist 
{
  my $grp = $_[0];
  my $naccts = gnucash::xaccGroupGetNumAccounts ($grp);
  my $n;
  my (@acctlist, @childlist);
  my $children;

  foreach $n (0..$naccts-1)
  {
    $acct = gnucash::xaccGroupGetAccount ($grp, $n);
    push (@acctlist, $acct);

    $children = gnucash::xaccAccountGetChildren ($acct);
    if ($children)
    {
      @childlist = &account_flatlist ($children);
      push (@acctlist, @childlist);
    }
  }

  return (@acctlist);
}

# --------------------------------------------------
# &checkprice ($account, $date);
# This routine checks to see if the account has already stored a
# price for this day, returning 1 if yes otherwise 0/undefined.

sub checkprice
{
  my $acct  = $_[0];
  my $dayte = $_[1];
  my ($query, $datesecs, $earliest, $latest);
  my ($splitlist, $i, $split, $action);

  $datesecs = gnucash::xaccScanDateS ($dayte);
  $earliest = $datesecs - 16*3600;        # subtract 16 hours
  $latest = $datesecs + 16*3600;          # add 16 hours
  $query = gnucash::xaccMallocQuery();
  gnucash::xaccQueryAddAccount ($query, $acct);
  gnucash::xaccQuerySetDateRange ($query, $earliest, $latest);
  $splitlist = gnucash::xaccQueryGetSplits ($query);

  $i = 0;
  $split = gnucash::IthSplit ($splitlist, $i);
  while ($split)
  {
    $action = gnucash::xaccSplitGetAction ($split);
    if ($action eq "Price") { return 1; }
    $i++;
    $split = gnucash::IthSplit ($splitlist, $i);
  }

  gnucash::xaccFreeQuery ($query);

  return 0;
}

# --------------------------------------------------
# &setprice ($account, $date, $price);
# This routine stores the indicated price in the indicated account.
# Handy little utility avoids the usual hassle.

sub setprice 
{
  my $acct  = $_[0];
  my $dayte = $_[1];
  my $price = $_[2];
  my ($trans, $split);

  $trans = gnucash::xaccMallocTransaction();
  gnucash::xaccTransBeginEdit ($trans, 1);
  gnucash::xaccTransSetDescription ($trans, "Price");
  gnucash::xaccTransSetDateStr ($trans,$dayte);

  $split = gnucash::xaccTransGetSplit ($trans, 0);
  gnucash::xaccSplitSetSharePriceAndAmount ($split, $price, 0.0);
  gnucash::xaccSplitSetAction ($split, "Price");
  gnucash::xaccTransCommitEdit ($trans);

  gnucash::xaccAccountBeginEdit ($acct, 1);
  gnucash::xaccAccountInsertSplit ($acct, $split);
  gnucash::xaccAccountCommitEdit ($acct);

  return 1;
}

# --------------------------------------------------

die "Usage: $0 <gnucash-filename>" if $#ARGV < 0;

# open the file, read all of the accounts
print "Opening file $ARGV[0]\n";
$sess = gnucash::xaccMallocSession ();
$grp = gnucash::xaccSessionBeginFile ($sess,$ARGV[0]);

die "failed to read file $ARGV[0], maybe its locked? " if (! $grp);

# get a flat list of accounts in the file
@acctlist = &account_flatlist ($grp);

# loop over the accounts, look for stock and mutual funds.
foreach $acct (@acctlist)
{
  $name = gnucash::xaccAccountGetName ($acct);
  $type = gnucash::xaccAccountGetType ($acct);

  if (($type == $gnucash::STOCK) ||
      ($type == $gnucash::MUTUAL))
  {
    print "$name: ";

    $security = gnucash::xaccAccountGetSecurity ($acct);
    $accinfo = gnucash::xaccAccountGetAccInfo ($acct);
    $invacct = gnucash::xaccCastToInvAcct ($accinfo);

    if ($invacct)
    {
      $quotesrc = gnucash::xaccInvAcctGetPriceSrc ($invacct);
      unless ($quotesrc)
      {
        print "no quote source found\n";
        next;
      }

      undef $price;  # undef to make sure later if($price) not broken

      if ("YAHOO" eq $quotesrc)
      {
        %quotes = &Quote::yahoo ($security);
        $price = $quotes {$security, "last"};
      }
      elsif ("YAHOO_EUROPE" eq $quotesrc)
      {
        %quotes = &Quote::yahoo_europe ($security);
        $price = $quotes {$security, "last"};
      }
      elsif ("FIDELITY" eq $quotesrc)
      {
        %quotes = &Quote::fidelity ($security);
        $price = $quotes {$security, "nav"};
      }
      elsif ("TRPRICE" eq $quotesrc)
      {
        %quotes = &Quote::troweprice ($security);
        $price = $quotes {$security, "nav"};
      }
      elsif ("VANGUARD" eq $quotesrc)
      {
        %quotes = &Quote::vanguard ($security);
        $price = $quotes {$security, "nav"};
      }
      else
      {
        print "unknown quote source: $quotesrc\n";
        next;
      }

      print "using $quotesrc\n";
      print "$name: ";

      $dayte = $quotes {$security, "date"};
      $prodname = $quotes {$security, "name"};

      if ($price)
      {
        print "$security $prodname last price = $price at $dayte\n";
        # This || construction will store the price if its not already 
        # stored (in the 28 hour period surrounding "dayte")
        &checkprice ($acct, $dayte) || &setprice ($acct, $dayte, $price);
      }
    }
  }
}

gnucash::xaccSessionSave ($sess);
gnucash::xaccSessionEnd ($sess);
