#!/usr/bin/perl

use strict;
use warnings;

use Getopt::Std;

use SudokuGenerator;
use SudokuSolver;
use SudokuType;

sub HELP_MESSAGE {
  my $script = $0;
  $script =~ s|^.*/||;
  print<<HELP

Usage: $script -[options|f:format] <input>

Option flags are:
 -h print help
 -l input has 1 puzzle per line
 -v print solution underneath the puzzle, not side by side
 -s print solutions only, not the puzzle

Output format options (-f) are:
  'preserve' (default):  stick to the input format
  'oneline':             all cell values concatenated
  'compact':             cells only, omitting regions
  'default':             vanilla 9x9

HELP
;
  exit(1);
}

my %format_names = (
  'default'  => 'DEFAULT',
  'oneline'  => 'ONELINE',
  'compact'  => 'COMPACT',
  'preserve' => 'PRESERVE'
);

my $opt_format = 'PRESERVE';
my $opt_one_sudoku_per_line = 0;
my $opt_side_by_side = 1;
my $opt_print_solved_only = 0;

my %opts;
getopts('f:hlsv', \%opts)
  or HELP_MESSAGE();

if ($opts{'h'}) {
  HELP_MESSAGE();
}

if ($opts{'l'}) {
  $opt_one_sudoku_per_line = 1;
}

if ($opts{'s'}) {
  $opt_print_solved_only = 1;
}

if ($opts{'v'}) {
  $opt_side_by_side = 0;
}

if ($opts{'f'}) {
  if (exists $format_names{$opts{'f'}}) {
    $opt_format = $format_names{$opts{'f'}};
  } else {
    print "Invalid argument for -f '$opts{'f'}'";
    HELP_MESSAGE();
  }
}

my $generator = SudokuGenerator->new();
my $input = '';
my $first = 1;

while (my $line = <STDIN>) {
  chomp $line;

  if ($line ne '') {
    $input .= $line;
    if (!$opt_one_sudoku_per_line) {
      $input .= "\n";
      next unless eof(STDIN);
    }
  }

  $input or next;

  eval {
    my $type = SudokuType::guess($input);

    my $format;
    if ($opt_format eq 'PRESERVE') {
      $format = SudokuFormat->new($type, $input);
    } elsif ($opt_format eq 'COMPACT') {
      $format = SudokuFormat->compact($type);
    } elsif ($opt_format eq 'ONELINE') {
      $format = SudokuFormat->oneline($type);
    } else {
      $format = SudokuFormat->new($type);
    }

    if (!$first && $opt_format ne 'ONELINE') {
      print "\n";
    }
    $first = 0;

    my $sudoku = Sudoku->new($type, $input);
    if ($sudoku->is_empty()) {
      $sudoku = $generator->generate($type);
    }

    my $solved = SudokuSolver::solve($sudoku);
    if (!$opt_print_solved_only && $opt_side_by_side) {
      print_side_by_side($sudoku->to_string_format($format), $solved->to_string_format($format));

    } else {
      if (!$opt_print_solved_only) {
        print $sudoku->to_string_format($format);
        if ($opt_format ne 'ONELINE') {
          print "\n";
        }
      }
      print $solved->to_string_format($format);
    }
  };

  if ($@) {
    print STDERR "\nERROR! $@\n";
    print STDERR $input, "\n";
  }

  $input = '';
}

sub print_side_by_side {
  my ($left, $right) = @_;

  my @ls = split("\n", $left);
  my @rs = split("\n", $right);
  my $max_left = 0;
  foreach my $l (@ls) {
    $max_left = length($l) if length($l) > $max_left;
  }

  my $max_lines = scalar(@ls) > scalar(@rs) ? scalar(@ls) : scalar(@rs);
  for (my $y = 0; $y < $max_lines; $y++) {
    my $pos = 0;
    if ($y < scalar(@ls)) {
      print $ls[$y];
      $pos = length($ls[$y]);
    }
    if ($y < scalar(@rs)) {
      print ' ' x (4 + $max_left - $pos);
      print $rs[$y];
    }
    print "\n";
  }
}

__END__

=encoding UTF-8

=head1 Sudoku

If something is worth doing, it's worth doing well.

The L<solver|examples/sudoku/SudokuSolver> itself is quite simple. The exact cover problem has four types of columns, n*n of each type. (For standard Sudoku, n = 9.)

=over

=item * Cell (I<x>, I<y>) needs to contain a digit.

=item * Column I<x> needs to contain digit I<d>.

=item * Row I<y> needs to contain digit I<d>.

=item * Region I<i> needs to contain digit I<d>.

=back

The first one can actually be either a secondary or a primary column; if all other conditions are met, every cell will naturally contain I<at most one> digit.

Each row of the matrix hits exactly one column of each type.

For the standard 9x9 Sudoku, all regions are 3x3 squares. But it is straight-forward to generalize this so that the regions can form an arbitrary partition of the grid to subsets of size n. This also makes it possible to create Sudokus of any size; with square regions, the closest alternatives to 9x9 are 4x4 and 16x16.

=head1 Usage

The executable tries to infer as much as possible from the input data, requiring no flags or meta data about the Sudoku types. It expects to find Sudokus separated by empty lines. Empty cells can be either dots (C<'.'>) or zeros (C<'0'>). All non-zero digits and letters can be used as labels. Regions can be drawn with any non-space non-label characters.

The given Sudokus will be solved, unless they are invalid or unsolvable; in that case, an error message is printed. A completely empty Sudoku will be replaced with a randomly generated (and usually quite difficult) one, which is also solved.

=head1 Examples

  examples/sudoku$ perl -I. ./sudoku.pl < ../data/sudoku.txt
  
  examples/sudoku$ perl -I. ./sudoku.pl -s -l -f oneline < sudoku17* > sudoku17_solutions
  
  examples/sudoku$ yes . | head -n81 | perl -I. ./sudoku.pl -f default

(*) L<http://staffhome.ecm.uwa.edu.au/~00013890/sudoku17>

=cut

