# ABSTRACT: Turns figlets into styled figlets
use strict;
use warnings;
package Text::ASCII::Stylize;
use Moose;
use YAML::Tiny;
use Try::Tiny;
use Getopt::Long;
use Clone 'clone';
use Data::Dumper;

use constant DEFAULT_FORMAT => 'id-software';

has 'in' => (
	is => 'rw',
	isa => 'ArrayRef'
);

has 'dib' => (
	is => 'rw',
	isa => 'ArrayRef'
);

has 'stack' => (
	is => 'rw',
	isa => 'ArrayRef'	
);

has 'yaml' => (
	is => 'rw',
	isa => 'Object'
);

has 'format' => (
	is => 'rw',
	isa => 'Str',
);

has 'do_gap' => (
	is => 'rw',
	isa => 'Bool'
);

has 'do_letters' => (
	is => 'rw',
	isa => 'Bool'
);

has 'do_fill' => (
	is => 'rw',
	isa => 'Bool'
);

has 'do_pad' => (
	is => 'rw',
	isa => 'Bool'
);

has 'do_shadow' => (
	is => 'rw',
	isa => 'Bool'
);

has 'do_margin' => (
	is => 'rw',
	isa => 'Bool'
);

has 'do_sequences' => (
	is => 'rw',
	isa => 'Bool'
);
sub main {
	my ($self) = @_;
	
	$self->proc_options();
	
	$self->defaults();
	
	$self->proc_yaml();
	
	$self->proc_in();	
	
	$self->proc_seq();
	
	$self->blit();		
}

sub put {		
	my ($self,$x,$y,$ch) = @_;		
	try {
		$self->dib->[$y]->[$x] = $ch;
	} catch {
		return(0);
	};		
}

sub get {
	my ($self,$x,$y) = @_;
	my $ret;
	
	return(0) if $x < 0 || $y < 0;
	
	try {
		$ret = $self->dib->[$y]->[$x];
	} catch {
		return(0);
	};

	return($ret) if $ret;
	return(0);
}

sub rio {
	my ($self,$x,$y,$ch,$w) = @_;				
	if(&$w) {
		$self->put($x,$y,$ch);
	}
}

sub rio_deferred {
	my ($self,$x,$y,$ch,$w) = @_;
	if(&$w) {
		my %item = ( x => $x, y=> $y,ch => $ch );		
		push(@{$self->stack}, \%item);
	}
}

sub put_stack {
	my ($self,$x,$y,$ch,$w) = @_;				
			
	my @stack = reverse(@{$self->stack});		
	for my $item (@stack) {
		$self->put($item->{x},$item->{y},$item->{ch});								
	}	
}

sub proc {
	my ($self) = @_;
	
	# fill
	if($self->do_fill) {
		$self->fill();
		$self->put_stack();
	}
	
	if($self->do_letters) {
		# full
		$self->letters_full();
		
		# horizontal
		$self->letters_left();
		$self->letters_right();
		
		# vertical
		$self->letters_top_half();	
		$self->letters_bottom_half();
		
		# diagonal
		$self->letters_top_left();
		$self->letters_bottom_left();
		$self->letters_top_right();		
		$self->letters_bottom_right();
	}
	
	#gap
	if($self->do_gap) {
		$self->gap();
		$self->put_stack();
	}
}

sub letters_full {
	my ($self) = @_;		
	my ($idx_x,$idx_y) = (0,0);
	for my $y (@{$self->dib}) {
		for my $x (@{$y}) {												
			$self->rio($idx_x,$idx_y,$self->yaml->[0]->{preset}->{letters}->{blocks}->{full},sub { 
				my $block = $self->get($idx_x,$idx_y);
				if($block =~ /[|\$&\()V\/\\=\:\<\>;8+#%MW]/) {
					1;
				} else {
					0;
				}
			});				
			$idx_x++;
		}			
		$idx_x = 0;
		$idx_y++;
	}
}

sub letters_top_half {
	my ($self) = @_;		
	my ($idx_x,$idx_y) = (0,0);
	for my $y (@{$self->dib}) {
		for my $x (@{$y}) {												
			$self->rio($idx_x,$idx_y,$self->yaml->[0]->{preset}->{letters}->{blocks}->{'top-half'},sub { 
				my $block = $self->get($idx_x,$idx_y);
				if($block =~ /[-'`*Y"]/) {
					1;
				} else {
					0;
				}
			});				
			$idx_x++;
		}			
		$idx_x = 0;
		$idx_y++;
	}
}

sub letters_top_left {
	my ($self) = @_;		
	my ($idx_x,$idx_y) = (0,0);
	for my $y (@{$self->dib}) {
		for my $x (@{$y}) {												
			$self->rio($idx_x,$idx_y,$self->yaml->[0]->{preset}->{letters}->{blocks}->{'top-left'},sub { 
				my $block = $self->get($idx_x,$idx_y);
				my $block_above = ($self->get($idx_x,$idx_y - 1) || ' ');
				my $block_left = ($self->get($idx_x - 1,$idx_y) || ' ');										
				if($block_left =~ /[\s]/ && $block_above =~ /[\s]/ && $block =~ /[^\s]/) {
					1;
				} else {
					0;
				}
			});				
			$idx_x++;
		}			
		$idx_x = 0;
		$idx_y++;
	}
}

sub letters_top_right {
	my ($self) = @_;		
	my ($idx_x,$idx_y) = (0,0);
	for my $y (@{$self->dib}) {
		for my $x (@{$y}) {												
			$self->rio($idx_x,$idx_y,$self->yaml->[0]->{preset}->{letters}->{blocks}->{'top-right'},sub { 
				my $block = $self->get($idx_x,$idx_y);
				my $block_above = ($self->get($idx_x,$idx_y - 1) || ' ');
				my $block_diag = ($self->get($idx_x + 1,$idx_y - 1) || ' ');
				my $block_right = ($self->get($idx_x + 1,$idx_y) || ' ');
				if($block_right =~ /[\s]/ && $block_above =~ /[\s]/ && $block_diag =~ /[\s]/ && $block =~  /[^\s]/) {
					return(1);
				} else {
					return(0);
				}
			});				
			$idx_x++;
		}			
		$idx_x = 0;
		$idx_y++;
	}
}

sub letters_left {
	my ($self) = @_;		
	my ($idx_x,$idx_y) = (0,0);
	for my $y (@{$self->dib}) {
		for my $x (@{$y}) {												
			$self->rio($idx_x,$idx_y,$self->yaml->[0]->{preset}->{letters}->{blocks}->{'left'},sub { 
				my $block = $self->get($idx_x,$idx_y);
				if($block =~  /[{\[]/) {
					return(1);
				} else {
					return(0);
				}
			});				
			$idx_x++;
		}			
		$idx_x = 0;
		$idx_y++;
	}
}

sub letters_right {
	my ($self) = @_;		
	my ($idx_x,$idx_y) = (0,0);
	for my $y (@{$self->dib}) {
		for my $x (@{$y}) {												
			$self->rio($idx_x,$idx_y,$self->yaml->[0]->{preset}->{letters}->{blocks}->{'right'},sub { 
				my $block = $self->get($idx_x,$idx_y);
				if($block =~  /[}\]]/) {
					return(1);
				} else {
					return(0);
				}
			});				
			$idx_x++;
		}			
		$idx_x = 0;
		$idx_y++;
	}
}

sub letters_bottom_left {
	my ($self) = @_;		
	my ($idx_x,$idx_y) = (0,0);
	for my $y (@{$self->dib}) {
		for my $x (@{$y}) {												
			$self->rio($idx_x,$idx_y,$self->yaml->[0]->{preset}->{letters}->{blocks}->{'bottom-left'},sub { 
				my $block = $self->get($idx_x,$idx_y);
				my $block_below = ($self->get($idx_x,$idx_y + 1) || ' ');
				my $block_diag = ($self->get($idx_x - 1,$idx_y + 1) || ' ');
				my $block_left = ($self->get($idx_x - 1,$idx_y) || ' ');
				if($block_left =~ /[\s]/ && $block_below =~ /[\s]/ && $block_diag =~ /[\s]/ && $block =~  /[^\s]/) {
					return(1);
				} else {
					return(0);
				}
			});				
			$idx_x++;
		}			
		$idx_x = 0;
		$idx_y++;
	}
}

sub letters_bottom_right {
	my ($self) = @_;		
	my ($idx_x,$idx_y) = (0,0);
	for my $y (@{$self->dib}) {
		for my $x (@{$y}) {												
			$self->rio($idx_x,$idx_y,$self->yaml->[0]->{preset}->{letters}->{blocks}->{'bottom-right'},sub { 
				my $block = $self->get($idx_x,$idx_y);
				my $block_below = ($self->get($idx_x,$idx_y + 1) || ' ');
				my $block_diag = ($self->get($idx_x + 1,$idx_y + 1) || ' ');
				my $block_right = ($self->get($idx_x + 1,$idx_y) || ' ');
				if($block_right =~ /[\s]/ && $block_below =~ /[\s]/ && $block_diag =~ /[\s]/ && $block =~  /[^\s]/) {
					return(1);
				} else {
					return(0);
				}
			});				
			$idx_x++;
		}			
		$idx_x = 0;
		$idx_y++;
	}
}

sub letters_bottom_half {
	my ($self) = @_;		
	my ($idx_x,$idx_y) = (0,0);
	for my $y (@{$self->dib}) {
		for my $x (@{$y}) {												
			$self->rio($idx_x,$idx_y,$self->yaml->[0]->{preset}->{letters}->{blocks}->{'bottom-half'},sub { 
				my $block = $self->get($idx_x,$idx_y);
				if($block =~ /[_,weaoxcnszmv]/) {
					return(1);
				} else {
					return(0);
				}
			});				
			$idx_x++;
		}			
		$idx_x = 0;
		$idx_y++;
	}
}

sub fill {
	my ($self) = @_;		
	my ($idx_x,$idx_y) = (0,0);
	for my $y (@{$self->dib}) {
		for my $x (@{$y}) {												
			$self->rio_deferred($idx_x,$idx_y,$self->yaml->[0]->{preset}->{letters}->{blocks}->{'fill'},sub { 					
				my $block = $self->get($idx_x,$idx_y);
				my $block_above = ($self->get($idx_x,$idx_y - 1) || ' ');
				my $block_below = ($self->get($idx_x,$idx_y + 1) || ' ');
				my $block_left = ($self->get($idx_x - 1,$idx_y) || ' ');
				my $block_right = ($self->get($idx_x + 1,$idx_y) || ' ');
				my $block_diag1 = ($self->get($idx_x - 1,$idx_y - 1) || ' ');
				my $block_diag2 = ($self->get($idx_x - 1,$idx_y + 1) || ' ');
				my $block_diag3 = ($self->get($idx_x + 1,$idx_y - 1) || ' ');
				my $block_diag4 = ($self->get($idx_x + 1,$idx_y + 1) || ' ');
				if(($block_below =~ /[\s]/ && $block_above =~ /[\s]/ && $block_right =~ /[\s]/ && $block_left =~ /[\s]/) && $block =~  /[\s]/ && $block_diag1 =~ /[\s]/ && $block_diag2 =~ /[\s]/ && $block_diag3 =~ /[\s]/ && $block_diag4 =~ /[\s]/) {						
					return(1);
				} else {
					return(0);
				}
			});				
			$idx_x++;
		}			
		$idx_x = 0;
		$idx_y++;
	}
}

sub gap {
	my ($self) = @_;		
	my ($idx_x,$idx_y) = (0,0);
	for my $y (@{$self->dib}) {
		for my $x (@{$y}) {
			$self->rio_deferred($idx_x,$idx_y,$self->yaml->[0]->{preset}->{letters}->{blocks}->{'gap'},sub { 					
				my $block = $self->get($idx_x,$idx_y);
				my $block_diag1 = ($self->get($idx_x - 1,$idx_y - 1) || ' ');
				my $block_diag2 = ($self->get($idx_x + 1,$idx_y - 1) || ' ');
				my $block_diag3 = ($self->get($idx_x - 1,$idx_y + 1) || ' ');
				my $block_diag4 = ($self->get($idx_x + 1,$idx_y + 1) || ' ');
				my $block_above = ($self->get($idx_x,$idx_y - 1) || ' ');
				my $block_below = ($self->get($idx_x,$idx_y + 1) || ' ');
				my $block_left = ($self->get($idx_x - 1,$idx_y) || ' ');
				my $block_right = ($self->get($idx_x + 1,$idx_y) || ' ');
				if((($block_below =~ /[^\s]/ && $block_above =~ /[^\s]/ && $block =~ /[\s]/) || ($block_left =~ /[^\s]/ && $block_right =~ /[^\s]/))&& ($block_diag1 =~ /[^\s]/ || $block_diag2 =~ /[^\s]/ || $block_diag3 =~ /[^\s]/ || $block_diag4 =~ /[^\s]/)) {						
					return(1);
				} else {
					return(0);
				}
			});				
			$idx_x++;
		}			
		$idx_x = 0;
		$idx_y++;
	}
	
	return(0);
}

sub proc_options {
    my $self = shift;    
    
	my $opt_format = DEFAULT_FORMAT;
	
	my $opt_do_fill = 0;
	my $opt_do_gap = 0;	
	my $opt_do_letters = 0;
	my $opt_do_pad = 0;
	my $opt_do_shadow = 0;
	my $opt_do_margin = 0;
	my $opt_do_sequence = 0;
	
    #eval {
        GetOptions(	
			'format=s' => \$opt_format,
			'do-pad' => \$opt_do_pad,
			'do-letters' => \$opt_do_letters,
			'do-fill' => \$opt_do_fill,
			'do-gap' => \$opt_do_gap,
			'do-shadow' => \$opt_do_shadow,
			'do-margin' => \$opt_do_margin,
			'do-sequences' => \$opt_do_sequence,
		);
        
		$self->format($opt_format);
		$self->do_pad($opt_do_pad);
		$self->do_letters($opt_do_letters);
		$self->do_fill($opt_do_fill);
		$self->do_gap($opt_do_gap);
		$self->do_shadow($opt_do_shadow) if $opt_do_pad;
		$self->do_margin($opt_do_margin) if $opt_do_pad;
		$self->do_sequences($opt_do_sequence) if $opt_do_pad;
    #};
  
    #die "Regrettably, this is not a valid format option.  Choose one of (id-software or seth-able)." if $@;
  
    return(0);
}

sub proc_seq {
	my ($self) = @_;
	$self->proc;
}

sub proc_yaml {
	my ($self) = @_;
	open(my $yaml_filehandle,'<','/home/jmcveigh/.text-ascii-stylize/' . $self->format . '.yml') or die('cannot open < format.yml');
	my @lines = <$yaml_filehandle>;
	close($yaml_filehandle);

	my $yaml = YAML::Tiny->read_string("@lines");		
	$self->yaml($yaml);		
}

sub defaults {
	my ($self) = @_;
	my @in = <>;
	my @stack = [];
	$self->in(\@in);
	$self->stack(@stack);	
}

sub blit {
	my ($self) = @_;	
	for my $y (@{$self->dib}) {
		for my $x (@{$y}) {								
			print $x if $x;
		}
		print "\n";
	}		
}

sub proc_in {
	my ($self) = @_;
	
	my @rows = @{$self->in};	
	my @dib;
	
	my @pad;
	my (@seq_top,@seq_bottom,@seq_right,@seq_left);
	my @shadow;
	my (@margin_bottom,@margin_top);
	my $max_length = 0;
	my ($margin,$margin_unset);
	
	($margin,$margin_unset) = (2,4) if $self->do_margin;
	($margin,$margin_unset) = (0,4) unless $self->do_margin;
	
	if($self->do_pad) {
		
		$max_length = length($rows[2]) + 3 if $self->do_pad;		
		$max_length = length($rows[2]) unless $self->do_pad;
		
		if($self->do_sequences) {
			my $seq_length = $max_length + 2;
			my $seq_height = $#rows + 2;
			my $idx_center = $seq_length / 2;
			my $idx_middle = $seq_height / 2;
			
			my $idx_top_seq_left_begin = 1;
			my $idx_top_seq_center_begin = $idx_center - (length($self->yaml->[0]->{preset}->{sequences}->{top}->{center}) / 2);
			my $idx_top_seq_right_begin = $seq_length - length($self->yaml->[0]->{preset}->{sequences}->{top}->{right});

			my $idx_bottom_seq_left_begin = 1;
			my $idx_bottom_seq_center_begin = $idx_center - (length($self->yaml->[0]->{preset}->{sequences}->{bottom}->{center}) / 2);
			my $idx_bottom_seq_right_begin = $seq_length - length($self->yaml->[0]->{preset}->{sequences}->{bottom}->{right});
		
			# blank row
			for(my $icount = 0;$icount < $seq_length;$icount++) {
				push(@seq_top,$self->yaml->[0]->{preset}->{letters}->{blocks}->{empty});
				push(@seq_bottom,$self->yaml->[0]->{preset}->{letters}->{blocks}->{empty});
			}
			
			# top left
			for(my $icount = $idx_top_seq_left_begin;$icount < $idx_top_seq_left_begin + length($self->yaml->[0]->{preset}->{sequences}->{top}->{left});$icount++) {
				$seq_top[$icount] = substr($self->yaml->[0]->{preset}->{sequences}->{top}->{left},$icount - $idx_top_seq_left_begin,1);
			}
			
			# top center
			for(my $icount = $idx_top_seq_center_begin;$icount < $idx_top_seq_center_begin + length($self->yaml->[0]->{preset}->{sequences}->{top}->{center});$icount++) {
				$seq_top[$icount] = substr($self->yaml->[0]->{preset}->{sequences}->{top}->{center},$icount - $idx_top_seq_center_begin,1);
			}
			
			# top right
			for(my $icount = $idx_top_seq_right_begin;$icount < $idx_top_seq_right_begin + length($self->yaml->[0]->{preset}->{sequences}->{top}->{right});$icount++) {
				$seq_top[$icount] = substr($self->yaml->[0]->{preset}->{sequences}->{top}->{right},$icount - $idx_top_seq_right_begin,1);
			}
			
			# bottom left
			for(my $icount = $idx_bottom_seq_left_begin;$icount < $idx_bottom_seq_left_begin + length($self->yaml->[0]->{preset}->{sequences}->{bottom}->{left});$icount++) {
				$seq_bottom[$icount] = substr($self->yaml->[0]->{preset}->{sequences}->{bottom}->{left},$icount - $idx_bottom_seq_left_begin,1);
			}
			
			# bottom center
			for(my $icount = $idx_bottom_seq_center_begin;$icount < $idx_bottom_seq_center_begin + length($self->yaml->[0]->{preset}->{sequences}->{bottom}->{center});$icount++) {
				$seq_bottom[$icount] = substr($self->yaml->[0]->{preset}->{sequences}->{bottom}->{center},$icount - $idx_bottom_seq_center_begin,1);
			}
			
			# bottom right
			for(my $icount = $idx_bottom_seq_right_begin;$icount < $idx_bottom_seq_right_begin + length($self->yaml->[0]->{preset}->{sequences}->{bottom}->{right});$icount++) {
				$seq_bottom[$icount] = substr($self->yaml->[0]->{preset}->{sequences}->{bottom}->{right},$icount - $idx_bottom_seq_right_begin,1);
			}
		}	
			
		push @pad,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};
		push @pad,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};		
		
		push @shadow,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};
		push @shadow,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};
		push @shadow,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};
		push @shadow,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad} if $self->do_margin;
		
		push @margin_top,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};
		push @margin_top,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};
		push @margin_top,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};
		
		push @margin_bottom,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};		
		push @margin_bottom,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};
		push @margin_bottom,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};		
		
		for(my $icount = 0;$icount < $max_length + $margin;$icount++) {
			push @pad,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};			
		}
		
		for(my $icount = 0;$icount < $max_length - ($margin_unset);$icount++) {
			push @shadow,$self->yaml->[0]->{preset}->{letters}->{blocks}->{'shadow'};
		}
		
		for(my $icount = 0;$icount < $max_length - $margin;$icount++) {				
			push @margin_bottom,$self->yaml->[0]->{preset}->{letters}->{blocks}->{'empty'};		
			push @margin_top,$self->yaml->[0]->{preset}->{letters}->{blocks}->{'empty'};
		}
		
		push @shadow,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad} if $self->do_margin;
		push @shadow,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};
		push @shadow,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};
		push @shadow,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};
		
		push @margin_top,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};
		push @margin_top,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};
		push @margin_top,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};
		
		push @margin_bottom,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};
		push @margin_bottom,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};
		push @margin_bottom,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};		
		
		push @dib,clone(\@seq_top) if $self->do_sequences;
		push @dib,clone(\@pad);		
		push @dib,clone(\@pad);
		push @dib,clone(\@shadow) if $self->do_shadow;			
		push @dib,clone(\@margin_top) if $self->do_margin;
	}
	
	for my $y (@rows) {	
		
		my @row;

		chomp($y);
		
		if($self->do_pad) {			 
			push @row,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};
			push @row,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};
			push @row,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};
			push @row,' ' if $self->do_margin;
		
			for(my $x = 0;$x < length($y);$x++) {			
				push @row,substr($y,$x,1);
			}			
		
			push @row,' ' if $self->do_margin;
			push @row,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};
			push @row,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};
			push @row,$self->yaml->[0]->{preset}->{letters}->{blocks}->{pad};
		} else {
			for(my $x = 0;$x < length($y);$x++) {			
				push @row,substr($y,$x,1);
			}
		}
		push @dib,\@row;
	}
	
	push @dib,clone(\@margin_bottom) if $self->do_margin;
	push @dib,clone(\@pad);		
	push @dib,clone(\@pad);
	push @dib,clone(\@shadow) if $self->do_shadow;
	push @dib,clone(\@seq_bottom) if $self->do_sequences;
	$self->dib(\@dib);		
}

1;