#########################################################################################
# Package        HiPi::Interface::MicroDotPHAT
# Description  : Interface to Pimoroni Micro Dot pHAT
# Copyright    : Perl Port Copyright (c) 2018 Mark Dootson
# License      : This is free software; you can redistribute it and/or modify it under
#                the same terms as the Perl 5 programming language system itself.
#
# This is a port of the Pimoroni Python code to Perl
#
# https://github.com/pimoroni/microdot-phat
#
#########################################################################################
# Pimoroni Copyright Notice
#########################################################################################
# MIT License
#
# Copyright (c) 2017 Pimoroni Ltd.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
#########################################################################################

package HiPi::Interface::MicroDotPHAT;

#########################################################################################
use strict;
use warnings;
use parent qw( HiPi::Interface );
use HiPi qw( :i2c :rpi :fl3730);
use HiPi::Interface::IS31FL3730;
use HiPi::Utils::BitBuffer;
use HiPi::Interface::MicroDotPHAT::Font qw( :font );
use Try::Tiny;
use Carp;

our $VERSION ='0.72';

__PACKAGE__->create_ro_accessors( qw( _hat_width _hat_height ) );

__PACKAGE__->create_accessors( qw(
    controllers buffer
    _rotate180
    _scrollx
    _scrolly
    _mirror
    _decimal
    _clear_on_exit
) );

my $matrixconfig = [
    { control => 0, type  => 'B' },
    { control => 0, type  => 'A' },
    { control => 1, type  => 'B' },
    { control => 1, type  => 'A' },
    { control => 2, type  => 'B' },
    { control => 2, type  => 'A' },
];

sub new {
    my ($class, %userparams) = @_;
    
    my %params = (
        brightness  => 127,
        width       => 30,
        height      => 7,
        _hat_width   => 30,
        _hat_height  => 7,
        _rotate180 => 0,
        _scrollx   => 0,
        _scrolly   => 0,
        _mirror    => 0,
        _clear_on_exit => 1,
        _decimal   => [ 0, 0, 0, 0, 0, 0 ],
    );
    
    # get user params
    foreach my $key( keys (%userparams) ) {
        my $paramkey = $key;
        $paramkey =~ s/^_+//;
        $params{$paramkey} = $userparams{$key};
    }
    
    # initialise
    my @controllers = ();
    
    for my $address ( 0x63, 0x62, 0x61 ) {
        my $control = HiPi::Interface::IS31FL3730->new( address => $address );
        $control->reset;
        $control->configure( FL3730_SSD_NORMAL | FL3730_DM_MATRIX_BOTH | FL3730_AEN_OFF | FL3730_ADM_8X8 );
        $control->lighting_effect( FL3730_AGS_0_DB | FL3730_CS_35_MA );
        $control->brightness( $params{brightness} );
        push @controllers, $control;
    }
    
    $params{controllers} = \@controllers;
    
    $params{buffer} = HiPi::Utils::BitBuffer->new(
        width       => $params{width},
        height      => $params{height},
        autoresize  => 1,
    );
    
    my $self = $class->SUPER::new(%params);
    HiPi->register_exit_method( $self, '_exit');
    return $self;
}

sub width { $_[0]->buffer->width; }

sub height { $_[0]->buffer->height; }

sub clear {
    my $self = shift;
    
    $self->buffer (
        HiPi::Utils::BitBuffer->new(
            width       => $self->width,
            height      => $self->height,
            autoresize  => 1,
        )
    );
    
    $self->_scrollx(0);
    $self->_scrolly(0);
    $self->_decimal([0,0,0,0,0,0]);
    return;
}

sub fill {
    my ( $self, $val ) = @_;
    $self->buffer->fill( $val );
}

sub set_rotate180 {
    my($self, $value) = @_;
    $self->_rotate180( $value ? 1 : 0 );
}

sub set_mirror {
    my($self, $value) = @_;
    $self->_mirror( $value ? 1 : 0 );
}

sub set_clear_on_exit {
    my($self, $value) = @_;
    $self->_clear_on_exit( $value ? 1 : 0 );
}

sub set_brightness {
    my($self, $val) = @_;
    
    $val ||= 1.0;
    if( $val > 1.0 || $val < 0.0 ) {
        carp q(brightness value must be between 0.0 and 1.0 );
    }
    
    my $brightness = int($val * 127);
    $brightness = 127 if $brightness > 127;
    
    $_->brightness( $brightness ) for ( @{ $self->controllers } );
}

sub set_col {
    my($self, $x, $col) = @_;
    
    for (my $y = 0; $y < 7; $y++) {
        $self->set_pixel($x, $y, ($col & (1 << $y)) > 0);
    }
}

sub set_pixel {
    my($self, $x, $y, $c) = @_;
    $c = $c ? 1 : 0;
    $self->buffer->set_bit($x, $y, $c);
}

sub write_char {
    my($self, $char, $offset_x, $offset_y) = @_;
    $offset_x ||= 0;
    $offset_y ||= 0;
    
    my $charbits = _get_char($char);
    for ( my $x = 0; $x < 5; $x++ ) {
        for ( my $y = 0; $y < 7; $y ++ ) {
            my $p = (($charbits->[$x] & (1 << $y)) > 0) ? 1 : 0;
            $self->set_pixel($offset_x + $x, $offset_y + $y, $p );
        }
    }
}

sub _get_char {
    my $char = shift;
    $char //= ' ';
    my $char_ordinal;

    try { $char_ordinal = ord($char); };
    
    unless( $char_ordinal && exists(phat_font->{$char_ordinal}) ) {
        carp qq(Unsupported char $char);
        $char_ordinal = 32;
    }

    # ? override
    
    $char_ordinal = 12316 if $char_ordinal == 65374;
    
    return phat_font->{$char_ordinal};
}


sub set_decimal {
    my($self, $index, $state) = @_;
    
    unless(defined($index)
           && $index =~ /^0|1|2|3|4|5$/
        ) {
        return;
    }
    
    $self->_decimal->[$index] = $state ? 1 : 0;
    
}

sub write_string {
    my($self, $string, $offset_x, $offset_y, $kerning ) = @_;
    $string   //= '';
    $offset_x ||= 0;
    $offset_y ||= 0;
    $kerning  //= 1;
    
    my $pixels = 0;
    for my $char ( split(//, $string) ) {

        my $char_data = _get_char($char);
        
        my @pixelcols = ();
        my ($maxX, $minX);
        
        for (my $x = 0; $x < 5; $x ++ ) {
            my @pixelrows = ();
            for ( my $y = 0; $y < 7; $y++ ) {
                
                my $val = (($char_data->[$x] & (1 << $y)) > 0) ? 1 : 0;
                
                if( $val ) {
                    $minX = $x unless(defined($minX));
                    $maxX = $x;
                }
                
                push @pixelrows, [ $offset_x + $x, $offset_y + $y, $val ];
            }
            
            push @pixelcols, \@pixelrows;
        }
        
        if( $kerning ) {
            if(defined($minX)) {
                my $shiftcount = $minX;
                for (my $x = 0; $x < $shiftcount; $x ++) {
                    shift @pixelcols;
                }
                for (my $x = $maxX; $x < 4; $x++) {

                    pop @pixelcols;
                }
                
                # adjust x values
                for my $col( @pixelcols ) {
                    for my $row ( @$col ) {
                        $row->[0] -= $shiftcount;
                    }
                }
                
                # add gap
                my @pixelrows = ();
                my $gapoffset = scalar @pixelcols;
                for ( my $y = 0; $y < 7; $y++ ) {
                    push @pixelrows, [ $offset_x + $gapoffset + 1, $offset_y + $y, 0 ];
                }
                push @pixelcols, \@pixelrows;
            } else {
                # a space - 2 rows - get rid of final 3
                pop @pixelcols;
                pop @pixelcols;
                pop @pixelcols;
            }   
        }
        
        my $charpixels = scalar @pixelcols;
        
        $offset_x += $charpixels;
        
        $pixels += $charpixels;
        
        for my $col ( @pixelcols ) {
            for my $row ( @$col ) {
                $self->set_pixel( @$row );
            }
        }
    }
    
    return $pixels;
}

sub show {
    my $self = shift;
    
    my $databuf = $self->buffer->clone_buffer;
    
    # scroll it etc
    $databuf->scroll_x_y( $self->_scrollx, $self->_scrolly);
    
    $databuf->mirror($self->_hat_width, $self->_hat_height) if $self->_mirror;
    
    $databuf->flip($self->_hat_width, $self->_hat_height) if $self->_rotate180;
    
    # write it
    for (my $matrix = 0; $matrix < 6; $matrix++) {
        my $mconf   = $matrixconfig->[$matrix];
        my $control = $self->controllers->[$mconf->{control}];
        my $offset_x = $matrix * 5;
                
        my @buffer = ( 0 ) x 8;
        
        for ( my $x = 0; $x < 5; $x++) {
            for ( my $y = 0; $y < 7; $y++) {
                my $val = $databuf->get_bit( $offset_x + $x, $y );
                if($mconf->{type} eq 'B') {
                    $buffer[$x] += ( $val << $y );
                } else {
                    $buffer[$y] += ( $val << $x );
                }
            }
        }
        
        if($mconf->{type} eq 'B') {
            if( $self->_decimal->[$matrix] ) {
                $buffer[7] |= 0b01000000;
            } else {
                $buffer[7] &= 0b10111111;
            }
            $control->matrix_2_data( @buffer );
        } else {
            if( $self->_decimal->[$matrix] ) {
                $buffer[6] |= 0b10000000;
            } else {
                $buffer[6] &= 0b01111111;
            }
            $control->matrix_1_data( @buffer );
        }
    }
    
    for my $control ( @{ $self->controllers } ) {
        $control->update;
    }
}

sub scroll {
    my($self, $amount_x, $amount_y) = @_;
    $amount_x //= 0;
    $amount_y //= 0;
    
    if($amount_x == 0 && $amount_y == 0 ) {
         $amount_x = 1;
    }
    
    my $scroll_x = $self->_scrollx;
    my $scroll_y = $self->_scrolly;

    $scroll_x += $amount_x;
    $scroll_y += $amount_y;
    $scroll_x %= $self->width;
    $scroll_y %= $self->height;
    
    $self->_scrollx( $scroll_x );
    $self->_scrolly( $scroll_y );
    return;
}

sub scroll_to {
    my($self, $position_x, $position_y) = @_;
    $position_x //= 0;
    $position_y //= 0;
    
    my $scroll_x = $position_x % $self->width;
    my $scroll_y = $position_y % $self->height;
    
    $self->_scrollx( $scroll_x );
    $self->_scrolly( $scroll_y );
    
    return;
}

sub scroll_horizontal {
    my($self, $amount) = @_;
    $amount //= 1;
    $self->scroll( $amount, 0 );
}

sub scroll_vertical {
    my($self, $amount) = @_;
    $amount //= 1;
    $self->scroll( 0, $amount );
}

sub draw_tiny {
    my($self, $display, $text) = @_;
    $text //= '';
    
    return unless( defined($display) && $display =~ /^0|1|2|3|4|5$/ );
    return unless length($text);
    
    unless( $text =~ /^\d+$/ ) {
        carp qq(text should contain only numbers: '$text');
        return;
    }
    
    my @buf = ();
    for my $char ( split(//, $text) ) {
        my $num = int($char);
        push @buf, @{ phat_tiny_numbers->[$num] };
        # space
        push @buf, 0;
    }
    
    my $rowcount = scalar @buf;
    $rowcount = 7 if $rowcount > 7;

    for ( my $row = 0; $row < $rowcount; $row ++ ) {
        my $offset_x = $display * 5;
        my $offset_y = 6-($row % 7);
        for ( my $d = 0; $d < 5; $d++ ) {
            $self->set_pixel($offset_x+(4-$d), $offset_y, ($buf[$row] & (1 << $d)) > 0);
        }
    }
    
    return;
}

sub _exit {
    my $self = shift;
    if( $self->_clear_on_exit ) {
        for my $control ( @{ $self->controllers } ) {
            $control->configure(FL3730_SSD_SHUTDOWN);
        }
    }
}

1;

__END__