#!/usr/bin/perl

use POSIX;
$|=1;

# worth.pl -- find out the value of your stock, and figure out how much
#             longer you have to wait until you're fully vested.
#
#             By Jamie Zawinski <jwz@netscape.com> 20-Sep-96.
#             (Still workin' for da man.)
#
#             DzM (dzm@dzm.com) March 1, 2000
#             Found a stock URL that works. Yay.
#
#             Rob Crittenden <rcrit@netscape.com> 15-Sep-00.
#             Fixed vesting schedule quarterly problem 01-Jun-01.
#
#             DzM June 12, 2001
#             Added Total Unsold Vested and Total Unvested Worth
#             Reformatted report into cleaner columns
#             Added inspiring text to follow report.
#             Cleaned up some of the docs/comments
#
#             DzM June 18, 2001
#             Added HTTP header and HTML formatting to allow
#             script to be used as a home page
#
#             DzM July 1, 2001
#             Stock URL retired. Found new functional URL
#
#             DzM July 3, 2001
#             Added "change from last close" line
#             Fixed broken averaging in summaries
#
#             DzM July 30, 2001
#             Fixed calculation problem that caused options
#             to continue vesting after being fully vested
#
#             DzM October 17, 2001
#             Added logic that describes how much the Ticker
#             needs to raise to give the soul value again.
#             Also cleaned up some of the style.
#             Added color to the Change value in the report
#             Provided percentage of options vested (total)
#
#             DzM October 30, 2001
#             Fixed a problem with the RegExp that caused stock
#             changes of whole numbers (1, 2, etc) to be combined
#             with Percentage Change (2.5, 3.0 etc). This led to
#             misleading numbers where a change of $1, or 3.2% was
#             represented as $13.2.
#
#             DzM August 26, 2002
#             Small change to accomodate format change of NASDAQ web
#             site.
#
#             DzM November 30, 2002
#             Fixed format error that would cause only first five digits
#             to be displayed if, by some lucky happenstance, your options
#             are not only not underwater but are also worth Real Actual Money.
#
#             PxT <paul@droflet.net> November 09, 2011
#             Minor fixes to accomodate nasdaq.com site changes 

# limitations:
#  - only handles stock from one company
#  - the rounding has a lot to be desired

# enhancements (rcrit/DzM):
#  added support for multiple stock grants
#  added support for yearly vesting
#  added summary reporting page
#  added stock change display
#  added CGI capabilities
#  added much fluffy verbage to underwater options

####
#### Stocks array.
####
#### The format is:
#### ( index, "strike price : shares : sold shares : vest start : vest end : schedule" )
####
#### For schedule, m = monthly spread evenly along the vesting schedule
####               a = annual vesting - split equal percentage vested accross
####                   vesting lifespan (i.e. 4 year vesting schedule, 1/4 per year)
####
#### for example:

%stocks=(1, "40.00:1000:0:04-01-1994:04-01-2002:m",
            2, "71.41:4000:0:4-29-1999:04-29-2003:a",
	    3, "85:1000:0:4-1-1998:4-5-2010:m",
	    4, "60:100:40:3-21-1999:2-5-2003:m"
           );

#### What company was it that you worked for again?
$ticker = "AOL";


#### You shouldn't need to change anything else.

############################################################################
############################################################################

#
# Print our CGI header and basic HTML formatting
#
# nasdaq.com now requires lower-case symbol names
$ticker = lc($ticker);

print "Content-type: text/html\n\n";
print "<html>\n<head>\n<title>\nSoldier Stat\n</title>\n</head>\n<body>\n";

#
# URL to acquire current price at
#

$quote_url ="http://www.nasdaq.com/symbol/". $ticker;

#
# Acquire HTML to get the current value of $ticker
#

use Socket;
sub http_grab {

  # mostly snarfed from wwwgrab.pl

  local ($_) = @_;

  /^http:\/\/([^\/]*)\/*([^ ]*)/;
  my $site = $1;
  my $file = "/".$2;

  if (!$site) {
    die "$0: non-HTTP URL: " . $_ . "\n";
  }

  $_ = $site;
  /^([^:]*):*([^ ]*)/;
  $site = $1;
  my $port = $2;
  $port = 80 unless $port;

  my $hostname = $site;

  # Open a socket and get the data
  my ($sockaddr,$there,$response,$tries) = ("Snc4x8");
  my $there = pack($sockaddr,2,$port, &getaddress($hostname));
#  my ($a, $b, $c, $d) = unpack('C4', $hostaddr);

  my $proto = (getprotobyname ('tcp'))[2];

  if (!socket(S,AF_INET,SOCK_STREAM,$proto)) {
    die "$0:  Fatal Error.  $!\n"; }
  if (!connect(S,$there)) { die "$0:  Fatal Error.  $!\n"; }
  select(S);$|=1;
  select(STDOUT);$|=1;
  print S "GET $file HTTP/1.0\r\n";
  print S "Host: www.nasdaq.com\r\n";
  print S "\r\n";
  while(<S>) {
    s/&nbsp\;/ /;
    if (m@Last|\$ [0-9]|>[0-9]\.|Change@) {  # Oh No! Pipe buffer fills up!
      print $_;
    }
  }
  close(S);

  sub getaddress {
    my($host) = @_;
    my(@ary);
    @ary = gethostbyname($host);
    return(unpack("C4",$ary[4]));
  }
}

#
# Get the URL, strip the price from the returned HTML
# This whole thing is RegExp voo-doo. Avoid it.
#

sub parse_url {
  my ($url) = @_;

  pipe(PIPEIN, PIPEOUT) || die "Can't make pipe stdout";

  open(SAVEOUT, ">&STDOUT");
  open(STDOUT, ">&PIPEOUT") || die "Can't redirect stdout";
  select(STDOUT); $| = 1;       # make unbuffered

  http_grab $url;

  close(STDOUT);
  open(STDOUT, ">&SAVEOUT");
  close(PIPEOUT);

  while (<PIPEIN>) {
    if ( m@Last Sale:@ ) {
      # read the next line
      $_ = <PIPEIN>;
      ( $per_share_value ) = m/\$ (\d+\.*\d*)</;
    }
    if ( m@Change Net\/%:@ ) {
      # read the next line
      $_ = <PIPEIN>;
      $down = m/class='red'/;
      /(\d+\.\d+)<\/label>/;

      $change = $1;
      $change = $change * -1 if ($down);
    }
  }
  if ($per_share_value == 0 || $per_share_value eq "" ) {
    die("Unable to find per-share price $per_share_value.\n");
  }
  close(PIPEIN);
}

#
# Beautify numbers (i.e. make 1000 be 1,000)
#

sub commify {
  local ($_) = shift;

  $_ = int($_);

  1 while s/^(-?\d+)(\d{3})/$1,$2/;
  return $_;
}

#
# Turn your puny readable dates into lovely mktime dates
#

sub parse_date {
  local ($_) = shift;
  
  local ($month, $day, $year) = split(/\-/);
  $month = $month - 1;
  $year = $year - 1900;
  #             sec, min, hour,   mday,    mon,   year,  wday, yday,  isdst
  return mktime(  0,   0,    0,   $day, $month,  $year,     0,    0,     0);
}

#
# Main
#

$today = time();
if ($#ARGV==0) {
  $per_share_value = $ARGV[0];
  $change = 0;
} else {
  parse_url $quote_url;
}

printf ( "<p>Today's " . $ticker . " price is \$%.2f (<font color=", $per_share_value );

if ( $change < 0 ) { # Make the number red if we're in negative range
  printf ( "#990000" );
}
elsif ( $change > 0 ) { # Make the number green if we're in positive range
  printf ( "#009900" );
} 
else { # Make the number black if it's sitting at zero
  printf ( "#000000" );
}

printf ( ">%.2f</font>)</p>\n\n", $change );
print "<pre>\n";

#
# Print the report header
#

print " Strike\t Total\t      %\t   Total  Unsold     Unsold   Unvested      Total\n";
print "  Price\tShares\t Vested\t  Vested  Vested\t  \$\t     \$   Unsold \$\n";
print " ------ ------ -------- -------- ------- ---------- ---------- ---------- \n";

#
# Prep the report numbers
#

$sum_totalshares = 0;
$sum_totalworth = 0;
$sum_unsoldworth = 0;
$sum_unsoldvested = 0;
$sum_unvestedworth = 0;
$sum_totalvested = 0;
$min_underwater = 0;

#
# Perform the heavy lifting and figure out how much our options suck
#

foreach $strike (keys %stocks) {
  #### ( "strike price : shares : sold shares : vest start : vest end : vest_schedule" )
  ($strike_price, $total_shares, $shares_sold, $vest_s, $vest_e, $schedule) = split(/:/, $stocks{$strike});

  $vest_start = &parse_date($vest_s);
  $vest_end = &parse_date($vest_e);

#
# Determine the type of vesting (monthly/annual) and perform magic
#

  if ($schedule eq "m") {
    $portion_done = int ($today - $vest_start) / ($vest_end - $vest_start);
  } else {
    $portion_done= 0 ;
    $diff = int ($today - $vest_start);
    $diff = ($diff / 3600 ) / 24;
    $remainder = $diff;
    while ($remainder > 365) {
      $portion_done++;
      $remainder -= 365;
    }
    $multiplier = $vest_end - $vest_start;
    $multiplier = int (($multiplier / 3600 ) / 24 / 365);
    $portion_done *= 1/$multiplier;
  }

  if ($today >= $vest_end) { # This keeps things from vesting after they hit 100%
    $shares_vested   = $total_shares;
    $shares_unvested = 0;
    $portion_done    = 1;
  } else {
    $shares_vested   = $total_shares * $portion_done;
    $shares_unvested = $total_shares - $shares_vested;
  }

  $shares_vested_and_unsold = $shares_vested - $shares_sold;

  $shares_vested = int($shares_vested);
  $shares_unvested = int($shares_unvested);
  $shares_vested_and_unsold = int($shares_vested_and_unsold);

  $sum_totalshares += $total_shares;
  $sum_unsoldvested += $shares_vested_and_unsold;

  $sum_totalvested += $shares_vested;

  $trade_value = ($per_share_value - $strike_price);

  if ( $trade_value > 0) {
    $sum_totalworth += ( $total_shares - $shares_sold ) * $trade_value;
    $sum_unsoldworth += $shares_vested_and_unsold * $trade_value;
    $sum_unvestedworth += ( $total_shares - $shares_vested ) * $trade_value;
    $sum_old_unvestedworth += ( $total_shares - $shares_vested ) * ( $trade_value + $change );
  } elsif (( $portion_done < 1 ) &&
	   (( $min_underwater == 0 ) || ( $min_underwater < $trade_value ))) {
    $min_underwater = $trade_value;
    $min_tanked_strike = $strike_price;
  }

#
# Print the numbers for a single grant
#

  printf("\$%6.2f %6.6s   %5.1f%%   %6.6s  %6.6s  \$%8.8s  \$%8.8s  \$%8.8s\n",
	 $strike_price,
	 commify ($total_shares),
	 ($portion_done * 100),
	 commify ($shares_vested),
	 commify ($shares_vested_and_unsold),
	 commify ($shares_vested_and_unsold * ($per_share_value - $strike_price)),
	 commify (($total_shares - $shares_vested) * ($per_share_value - $strike_price)),
	 commify (($total_shares - $shares_sold) * ($per_share_value - $strike_price))
	);
  }

print " ------ ------ -------- -------- ------- ---------- ---------- ---------- \n";

#
# Print the summary
#

printf("        %6.6s   %5.1f%%   %6.6s  %6.6s  \$%8.8s  \$%8.8s  \$%8.8s\n",
       commify ($sum_totalshares),
       ( 100 * ($sum_totalvested / $sum_totalshares)),
       commify ($sum_totalvested),
       commify ($sum_unsoldvested),
       commify ($sum_unsoldworth),
       commify ($sum_unvestedworth),
       commify ($sum_totalworth)
      );

print "</pre>\n\n";

#
# Determine the current worth of unvested grants. Print an inspiring
# message if the net is not under water - if the net IS under water
# print a different inspiring message
#

if ($sum_unvestedworth <= 0) {
  printf("<p>Your options are all worthless. Your soul has no value.<br>\n");
  printf($ticker . "'s price needs to go up at least ");
  printf("\$%.2f (\$%.2f) to give your soul value.<br>\n", 
	 (( -1 * $min_underwater ) + .01 ), ( $min_tanked_strike + .01 ));
  printf("Why are you still here little pawn?</p>\n");
  }
else
  {
    printf("<p>Be happy little pawn. No homicidal spree today. Your soul is worth \$%.8s.<br>\n",
	   commify($sum_unvestedworth));
    printf("That's ");

    $sum_unvestedchange = ($sum_old_unvestedworth - $sum_unvestedworth);

    if ($sum_unvestedchange < 0) {
      printf("down \$%.6s",
	     commify( 0 - $sum_unvestedchange ));
    }
    elsif ( $sum_unvestedchange == 0 ) {
	printf("no change");
      }
    else
      {
	printf("up \$%.6s",
	       commify( $sum_unvestedchange ));
      }

    printf(" from the last close.</p>\n\n");
  }

print "</body>\n</html>\n\n";

exit 0;
