#!/usr/bin/perl -T

# Usage: ./solar.pl profile.csv $i $j < forecast_file.csv
# where each data row from the forecast file has the following format:
#
# PvEstimate,PvEstimate10,PvEstimate90,PeriodEnd,Period
# 0.4645,0.3248,0.5801,2022-11-03T11:00:00Z,PT30M
#
# and each data row from the profile file has the following format:
#
# Date,Percentage of Daily Consumption
# 2022-11-02 01:00,3.12040112867697
#
# The $i parameter is the daily average power usage of the house in kWh
#
# The $j parameter is the usable battery capacity in kWh

use strict;
use warnings;
use DateTime;
use Scalar::Util qw(looks_like_number);

my @csv_forecast;
my @csv_profile;

# Check command line switches are fine.
die "Wrong command line" unless ( @ARGV == 3 );
die "Wrong second argument" unless looks_like_number($ARGV[1]);
if ( ($ARGV[1]) < 0 ) {
	die "Wrong second argument";
}
die "Wrong third argument" unless looks_like_number($ARGV[2]);
if ( ($ARGV[2] < 0 )) {
	die "Wrong second argument";
}

my $profile_path = $ARGV[0];
my $daily_power = $ARGV[1];
my $max_battery = $ARGV[2];
# Arbitrarily set the battery to mid-charge in the begining
my $battery = $max_battery / "2";

my $line_number = "0";
#open (my $fforecast, '<', $ARGV[0]) or die "Failed to open $ARGV[0]";
while ( my $line = <STDIN> ) {

	$line =~ s/\R/\012/;
	chomp ($line);

	my ($power,$timestamp,$period) = (split "," , $line)[0,3,4];
        if ($line_number == "0") {
		if ( ( $power ne 'PvEstimate' ) or ( $timestamp ne 'PeriodEnd')) {
			die "Forecast CSV file looks malformed.";
		}
        } else {
		$power =~ /^\d(\.\d+)?$/ or die "First column at row ++$line_number of the source file is malformed";
		$timestamp =~ /^(\d{4}\-\d{2}\-\d{2})(T\d{2}\:\d{2}\:\d{2}Z)?$/ or die "The date column at row ++$line_number has an unexpected format";
		$period =~ /^(PT30M)$/ or die "Fifth column at row ++$line_number of the source file has an unexpected value."
	}

	${csv_forecast[$line_number]}{"power"} = $power;
	${csv_forecast[$line_number]}{"timestamp"} = $timestamp;

	++$line_number;
}
#close ($fforecast);

$line_number = 0;
open (my $fprofile, '<', $ARGV[0]) or die "Failed to open $ARGV[0]";
while ( my $line = <$fprofile>) {

	chomp ($line);

	my ($timestamp,$percentage) = (split "," , $line);
	if ($line_number == "0") {
		if ( ($timestamp ne 'Date') or ($percentage ne 'Percentage of Daily Consumption')) {
		       die "Profile file looks malformed"
	       }
       } else {	       
	$percentage =~ /^[\d]*\.[\d]*$/ or die "Profile looks malformed";
	$timestamp =~ /^([\d]{4})\-([\d]{2})\-([\d]{2}) ([\d]{2})\:([\d]{2})$/ or die "Profile looks malformed.";
	${csv_profile[$line_number]}{"timestamp"} = $timestamp;
	${csv_profile[$line_number]}{"percentage"} = $percentage;
	}

	++$line_number;
}
close ($fprofile);

# For each 30 minutes period, calculate whether we are going to produce
# more power than we are going to consume (and therefore waste it),
# or whether we are going to consume more power than we produce (and
# therefore have deficit).

my $waste = "0";
my $deficit = "0";
my $produced = "0";

my ( $y,$m,$d,$h,$mt,$sec );
my ( $i,$j );
for ( $i = 1 ; $i < @csv_forecast ; ++$i )  {

	($y,$m,$d) = $csv_forecast[$i]{"timestamp"} =~ /^([\d]{4})\-([\d]{2})\-([\d]{2})/;
	($h,$mt,$sec) = $csv_forecast[$i]{"timestamp"} =~ /T([\d]{2})\:([\d]{2})\:([\d]{2})Z$/;
	unless ( defined ($h) ) { ($h,$mt,$sec) = ( '0', '0', '0') };
	my $forecast_time = DateTime->new(
		year => $y,
		month => $m,
		day => $d,
		hour => $h,
		minute => $mt,
		second => $sec,
		time_zone => 'UTC'
	);
	$forecast_time->set_time_zone('local');

	# Check if the forecast entries are placed in proper order
	if ( $i > "1" ) {
		my ( $oy,$om,$od,$oh,$omt,$osec );
		($oy,$om,$od) = $csv_forecast[$i-1]{"timestamp"} =~ /^([\d]{4})\-([\d]{2})\-([\d]{2})/;
		($oh,$omt,$osec) = $csv_forecast[$i-1]{"timestamp"} =~ /T([\d]{2})\:([\d]{2})\:([\d]{2})Z$/;
		unless ( defined ($oh) ) { ($oh,$omt,$osec) = ( '0', '0', '0') };
		my $oforecast_time = DateTime->new(
			year => $oy,
			month => $om,
			day => $od,
			hour => $oh,
			minute => $omt,
			second => $osec,
			time_zone => 'UTC'
		);
		$oforecast_time->set_time_zone('local');

		# Compare if old forecast timestamp is older than the one we are about to evaluate.
		die "The forecast file uses wrong ordering" if ( DateTime->compare($forecast_time,$oforecast_time) != 1 );
		# Check that the time difference is only 30 minutes
		die "The forecast file has a messed up timestamp" if ($forecast_time->subtract_datetime($oforecast_time)->minutes() != 30 );
	}
	
	my $consumption;
	my ( $hour,$minute );
	for ( $j = 1 ; $j < @csv_profile ; ++$j ) {
		($hour,$minute) = $csv_profile[$j]{"timestamp"} =~ /([\d]{2})\:([\d]{2})$/;

		if ( $hour == $forecast_time->hour() and $minute == $forecast_time->minute() ) {
			$consumption = $csv_profile[$j]{"percentage"} * $daily_power * 0.01;
			last;
		}

	}
	die "No consumption information in the profile file for timestamp " . $forecast_time->hour() . ":" . $forecast_time->minute() unless defined($consumption);
	if ( $consumption > ( $csv_forecast[$i]{"power"} * 0.5 )) {
		my $excess = $consumption - ($csv_forecast[$i]{"power"} * 0.5 );
		if ( ($battery - $excess) < "0" ) {
			$deficit += $excess - $battery;
			$battery = "0";
		} else {
			$battery -= $excess;
		}
	} else {
		my $excess = ($csv_forecast[$i]{"power"} * 0.5 ) - $consumption;
		if ( ($battery + $excess) > $max_battery) {
			$waste += $excess - ($max_battery - $battery);
			$battery = $max_battery;
		} else {
			$battery += $excess;
		}
	}

	$produced += $csv_forecast[$i]{"power"} * 0.5;

	# If the day has changed, print information for the day.
	if ( $forecast_time->hour() == "0" and $forecast_time->minute() == "0" ) {
		$forecast_time->subtract( days=> "1");
		print "INFORMATION FOR DATE ".  $forecast_time->date() . "\n";
		print "Total power produced will be $produced kWh\n";
		print "About $deficit kWh will be taken from the grid (deficit)\n";
		print "About $waste kWh will be produced but not used (wasted)\n";
		print "The charge of the battery by midnight will be " . ( $battery / $max_battery ) * 100 . "% \n\n";

		$deficit = 0;
		$waste = 0;
		$produced = 0;
	}
}

exit;