#!/usr/bin/perl

use strict;
use warnings;
use lib qw/lib/;
use Urb::Dom;
use Urb::Dom::Fitness;
use Urb::Dom::Mutate;
use File::Spec;
use YAML;
use Carp;
use 5.010;

local $SIG{INT} = \&interrupt;
local $SIG{TERM} = \&interrupt;

my $max_iterations = $ENV{MAX_ITERATIONS} || 1000;

my $path_yaml = shift || croak "Mutates an individual and keeps any mutations that increase fitness.
Useful environment variables:
  MAX_ITERATIONS=1000    varies the number of iterations
  CHECKPOINT=1           keeps a copy of all intermediate steps
  START_ITERATION=1001   Useful for continuing after checkpointing
Interrupting the tool writes the results to disk before exiting
Usage: $0 start.dom [mutated.dom]";
my $path_yaml_out = shift || 'temp.dom';
$path_yaml_out =~ s/\.dom$//xi;

my $overlay = undef;
if (-e 'occlusion.field')
{
    say STDERR 'Using occlusion overlay field';
    $overlay = Urb::Field::Occlusion->new;
    open my $YAML, '<', 'occlusion.field' or croak "$!";
    my $string = join '', (<$YAML>);
    close $YAML;
    $overlay->Deserialise ($string);
}

my $config = undef;
my $path_patterns = File::Spec->catfile (File::Spec->updir(), 'patterns.config');
if (-e $path_patterns)
{
    say STDERR 'Using project level patterns config file';
    $config = YAML::LoadFile ($path_patterns);
}
if (-e 'patterns.config')
{
    say STDERR 'Using patterns config file';
    my $config_temp = YAML::LoadFile ('patterns.config');
    for my $key (keys %{$config_temp})
    {
        $config->{$key} = $config_temp->{$key};
    }
}

my $costs = undef;
my $path_costs = File::Spec->catfile (File::Spec->updir(), 'costs.config');
if (-e $path_costs)
{
    say STDERR 'Using project level costs config file';
    $costs = YAML::LoadFile ($path_costs);
}
if (-e 'costs.config')
{
    say STDERR 'Using costs config file';
    my $costs_temp = YAML::LoadFile ('costs.config');
    for my $key (keys %{$costs_temp})
    {
        $costs->{$key} = $costs_temp->{$key};
    }
}

my $dom = Urb::Dom->new;
$dom->Deserialise (YAML::LoadFile ($path_yaml));

my $assessor = Urb::Dom::Fitness->new ($config, $costs);
$assessor->{_occlusion} = $dom->Occlusion ($overlay);
$assessor->{_occlusion}->{nocache} = 1;

my $mutator = Urb::Dom::Mutate->new;

my $rating = sprintf ("%.40f", $assessor->_apply ($dom));
my $rating_original = $rating;
print "original: $rating_original\n";
my $improvements = 0;
my $iterations_unproductive = 0;
my $start_iteration = $ENV{START_ITERATION} || 0;

for my $iteration ($start_iteration .. $max_iterations)
{
    my $dom_new = $dom->Clone;
    $iterations_unproductive++;
    for (1 .. int (log ($iterations_unproductive + 20) / log (10)))
    {
        my $dom_extra = $mutator->apply_shuffle($dom_new);
        $dom_new->DESTROY;
        $dom_new = $dom_extra;
    }
    my $rating_new = sprintf ("%.40f", $assessor->_apply ($dom_new));
    print "iteration: $iteration/$max_iterations ($iterations_unproductive): $rating_new\n";
    if ($rating_new <= $rating) {$dom_new->DESTROY; next}
    $improvements++;
    print "improvements: $improvements; $rating_original -> $rating -> $rating_new\n";
    $rating = $rating_new;
    $dom->DESTROY;
    $dom = $dom_new;
    $iterations_unproductive = 0;
    finish ($path_yaml_out .'_'. sprintf("%08d", $iteration)) if $ENV{CHECKPOINT};
}

finish ($path_yaml_out);

sub interrupt
{
    say STDERR "[$$] caught signal";
    finish ($path_yaml_out);
    exit 0;
}

sub finish
{
    my $path = shift;
    if ($path eq 'temp')
    {
        $path = $dom->Hash;
    }
    print "improvements: $improvements; $rating_original -> $rating\n";
    YAML::DumpFile ($path .'.dom', $dom->Serialise);
    say $path;
    unlink "$path.dom.score";
    open my $SCORE, '>', "$path.dom.score" or croak "$!";
    $rating = sprintf ("%.40f", $rating);
    print $SCORE "$rating\n";
    close $SCORE;

    # get list of failures generated during scoring, write to file
    my @failures = $dom->Failures;

    unlink "$path.dom.fails";
    open my $FAILS, '>', "$path.dom.fails" or croak "$!";
    print $FAILS join "\n", @failures, '';
    close $FAILS;
    return;
}

exit 0;
