package Sudoku;

use common::sense;
use Image::Magick;

BEGIN {
  our @attributes=(qw/cellsz margin _im/);
  for(my $i=0; $i<@attributes; $i++) {
    my $n=$i;
    no strict 'refs';
    *{__PACKAGE__.'::'.$attributes[$n]}=sub : lvalue {$_[0]->[$n]};
  }
}

sub new {
  my $class=$_[0];
  my $I=bless [], ref($class)||$class;

  my $tmp;
  for( my $i=1; $i<@_; $i+=2 ) {
    $tmp=$I->can($_[$i]) and $I->$tmp=$_[$i+1];
  }

  $I->cellsz||=80;
  $I->margin||=$I->cellsz/10;

  return $I;
}

sub board9x9 {
  my ($I)=@_;

  my ($cellsz, $margin)=($I->cellsz, $I->margin);
  my $borderw=$cellsz/20;

  $I->_im=my $img=Image::Magick->new
    (size=>"@{[9*$cellsz+2*$margin]}x@{[9*$cellsz+2*$margin]}");
  $img->Read('xc:white');

  my $off=$margin;
  my @path=("M $off $off                l @{[9*$cellsz]} 0",
            "M $off @{[9*$cellsz+$off]} l @{[9*$cellsz]} 0",
            "M $off $off                l 0 @{[9*$cellsz]}",
            "M @{[9*$cellsz+$off]} $off l 0 @{[9*$cellsz]}",
           );
  $img->Draw(stroke=>'black', strokewidth=>$borderw, primitive=>'path',
             points=>join(' ', @path));

  @path=("M $off @{[3*$cellsz+$off]} l @{[9*$cellsz]} 0",
         "M $off @{[6*$cellsz+$off]} l @{[9*$cellsz]} 0",
         "M @{[3*$cellsz+$off]} $off l 0 @{[9*$cellsz]}",
         "M @{[6*$cellsz+$off]} $off l 0 @{[9*$cellsz]}",
        );
  $img->Draw(stroke=>'black', strokewidth=>$borderw/2, primitive=>'path',
             points=>join(' ', @path));

  @path=("M $off @{[1*$cellsz+$off]} l @{[9*$cellsz]} 0",
         "M $off @{[2*$cellsz+$off]} l @{[9*$cellsz]} 0",
         "M $off @{[4*$cellsz+$off]} l @{[9*$cellsz]} 0",
         "M $off @{[5*$cellsz+$off]} l @{[9*$cellsz]} 0",
         "M $off @{[7*$cellsz+$off]} l @{[9*$cellsz]} 0",
         "M $off @{[8*$cellsz+$off]} l @{[9*$cellsz]} 0",
         "M @{[1*$cellsz+$off]} $off l 0 @{[9*$cellsz]}",
         "M @{[2*$cellsz+$off]} $off l 0 @{[9*$cellsz]}",
         "M @{[4*$cellsz+$off]} $off l 0 @{[9*$cellsz]}",
         "M @{[5*$cellsz+$off]} $off l 0 @{[9*$cellsz]}",
         "M @{[7*$cellsz+$off]} $off l 0 @{[9*$cellsz]}",
         "M @{[8*$cellsz+$off]} $off l 0 @{[9*$cellsz]}",
        );
  $img->Draw(stroke=>'black', strokewidth=>$borderw/4, primitive=>'path',
             points=>join(' ', @path));

  return $I;
}

sub cages {
  my ($I, $fh)=@_;
  my ($img, $cellsz, $margin)=($I->_im, $I->cellsz, $I->margin);
  my $off=$I->cellsz/10;
  while(defined( my $l=readline $fh )) {
    next if $l=~/^\s*(?:#|$)/;
    $l=~s/^(\d+)\s+(\d+)\s+(\S+)\s*// and my ($cx, $cy, $caption)=($1, $2, $3);
    my @p=split /\s+/, $l;
    my @path=("M @{[$cx*$cellsz+$margin+$off]} @{[$cy*$cellsz+$margin+$off]}");
    my $mode=0;                 # horizontal
    my $so=$off;
    while(my $len=shift @p) {
      my $eo=$off;
      if(@p) {
        if( $mode ) {           # vertical
          $eo=-$eo if $len<0 && $p[0]<0 or $len>0 && $p[0]>0;
        } else {                # horizontal
          $eo=-$eo if $len<0 && $p[0]>0 or $len>0 && $p[0]<0;
        }
      }

      # warn "len=$len: so=$so eo=$eo\n";
      if( $mode ) {             # vertical
        push @path, "l 0 @{[$len*$cellsz+($len<0?1:-1)*($so+$eo)]} m 0 0";
      } else {                  # horizontal
        push @path, "l @{[$len*$cellsz+($len<0?1:-1)*($so+$eo)]} 0 m 0 0";
      }
      $so=$eo;

      $mode=!$mode;
    }
    $img->Draw(stroke=>'black', strokewidth=>$cellsz/120, primitive=>'path',
               'stroke-dasharray'=>[9,6], fill=>'transparent',
               points=>join(' ', @path));

    my $txt=Image::Magick->new(size=>"${cellsz}x${cellsz}");
    $txt->Read('xc:white');
    my @dim=$txt->QueryFontMetrics( x=>0, y=>0,
                                    font=>'FreeSans-Medium',
                                    pointsize=>$cellsz*28/80,
                                    text=>"$caption" x 10, fill=>'black',
                                    strokewidth=>0 );
    # warn "$caption: @dim\n";
    # @dim=@dim[9,10];
    @dim=($dim[4]/20, $dim[5]);
    $txt->Annotate( x=>$cellsz/80, y=>0, gravity=>'SouthWest',
                    font=>'FreeSans-Medium',
                    pointsize=>$cellsz*14/80,
                    text=>$caption, fill=>'black',
                    strokewidth=>0 );
    # $txt->display;
    $txt->Crop(x=>0,
               y=>int($cellsz-$dim[1]*0.45),
               width=>int($dim[0]+$cellsz*5/80),
               height=>int($dim[1]+$cellsz*2/80));
    # $txt->display;
    $img->Composite(image=>$txt, compose=>'Atop',
                    x=>int($cx*$cellsz+$margin+$off-$cellsz*2/80),
                    y=>int($cy*$cellsz+$margin+$off-$cellsz*5/80));
  }

  return $I;
}

sub write_png {
  my $img=$_[0]->_im;
  $img->SetAttribute(magick=>'png');
  binmode STDOUT;
  $img->Write('-');

  return $_[0];
}

sub example {
  __PACKAGE__->new(@_)->board9x9->cages(\*DATA)->write_png;
}

1;

__DATA__
# Format:
# - one complete cage per line
# - the first 2 numbers specify the x and y coordinates of the cell where the
#   cage starts. This cell also contains the cage sum in the upper left corner.
# - the 3rd number is the cage sum
# - all the following numbers describe the cage in terms of altering N cells
#   to the right and N cells down. The first extension is always to the right.
#
# Example:
#
# 29-+--+--+  Assuming this cage starts in row 4 column 2. The description
# |        |  line then would be: 1 3 29 3 1 -1 2 -1 -2 -1 -1
# +--+  +--+  Coordinates are 0-based. Hence, they are x=1 (columns) and
#    |  |     y=3 (rows). The cage sum is 29. From its starting cell the
#    +  +     cage extends then 3 cells to the right and 1 cell down. Then
#    |  |     comes 1 cell to the left or -1 to the right. Then 2 down,
#    +--+     -1 right, -2 down, -1 right and the closing -1 down.


# 1st row
0 0 3 2 1 -2 -1
2 0 15 3 1 -3 -1
5 0 22 1 2 -1 1 -1 -2 1 -1
6 0 4 1 2 -1 -2
7 0 16 1 2 -1 -2
8 0 15 1 4 -1 -4

# 2nd row
0 1 25 2 2 -2 -2
2 1 17 2 1 -2 -1

# 3rd row
2 2 9 2 2 -1 -1 -1 -1
5 2 8 1 3 -1 -3
6 2 20 2 1 -1 1 -1 -2

# 4th row
0 3 6 1 2 -1 -2
1 3 14 2 1 -2 -1
4 3 17 1 3 -1 -3
7 3 17 1 2 -2 -1 1 -1

# 5th row
1 4 13 2 1 -1 1 -1 -2
3 4 20 1 3 -1 -3
8 4 12 1 2 -1 -2

# 6th row
0 5 27 1 4 -1 -4
2 5 6 1 2 -2 -1 1 -1
5 5 20 1 1 1 1 -2 -2
6 5 6 2 1 -2 -1

# 7th row
4 6 10 1 2 -1 1 -1 -2 1 -1
7 6 14 2 2 -2 -2

# 8th row
1 7 8 1 2 -1 -2
2 7 16 1 2 -1 -2
5 7 15 2 1 -2 -1

# 9th row
4 8 13 3 1 -3 -1
7 8 17 2 1 -2 -1
