package LatexIndent::Lines;

#	This program is free software: you can redistribute it and/or modify
#	it under the terms of the GNU General Public License as published by
#	the Free Software Foundation, either version 3 of the License, or
#	(at your option) any later version.
#
#	This program is distributed in the hope that it will be useful,
#	but WITHOUT ANY WARRANTY; without even the implied warranty of
#	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#	GNU General Public License for more details.
#
#	See http://www.gnu.org/licenses/.
#
#	Chris Hughes, 2017-2025
#
#	For all communication, please visit: https://github.com/cmhughes/latexindent.pl
use strict;
use warnings;
use Exporter              qw/import/;
use LatexIndent::LogFile  qw/$logger/;
use LatexIndent::Switches qw/%switch/;
use LatexIndent::Verbatim qw/%verbatimStorage/;
our @ISA       = "LatexIndent::Document";    # class inheritance, Programming Perl, pg 321
our @EXPORT_OK = qw/lines_body_selected_lines lines_verbatim_create_line_block/;

sub lines_body_selected_lines {
    my $self  = shift;
    my @lines = @{ $_[0] };

    # strip all space from lines switch
    $switch{lines} =~ s/\h//sg;

    # convert multiple - into single
    $switch{lines} =~ s/-+/-/sg;

    $logger->info("*-n,--lines switch is active, operating on lines $switch{lines}");
    $logger->info( "number of lines in file: " . ( $#lines + 1 ) );
    $logger->info("*interpreting $switch{lines}");

    my @lineRanges = split( /,/, $switch{lines} );
    my @indentLineRange;
    my @NOTindentLineRange;

    my %minMaxStorage;
    my %negationMinMaxStorage;

    # loop through line ranges, which are separated by commas
    #
    #       --lines 3-15,17-19
    #
    foreach (@lineRanges) {
        my $minLine      = 0;
        my $maxLine      = 0;
        my $negationMode = 0;

        #
        #  --lines !3-15
        #
        if ( $_ =~ m/!/s ) {
            $negationMode = 1;
            $_ =~ s/!//s;
            $logger->info("negation mode active as $_");
        }

        #  --lines min-max
        if ( $_ =~ m/-/s ) {
            ( $minLine, $maxLine ) = split( /-/, $_ );
        }
        else {
            $minLine = $_;
            $maxLine = $_;
        }

        # both minLine and maxLine need to be INTEGERS
        if ( $minLine !~ m/^\d+$/ or $maxLine !~ m/^\d+$/ ) {
            $logger->warn("*$_ not a valid line specification; I'm ignoring this entry");
            next;
        }

        # swap minLine and maxLine if necessary
        if ( $minLine > $maxLine ) {
            ( $minLine, $maxLine ) = ( $maxLine, $minLine );
        }

        # minline > number of lines needs addressing
        if ( $minLine - 1 > $#lines ) {
            $logger->warn(
                "*--lines specified with min line $minLine which is *greater than* the number of lines in file: "
                    . ( $#lines + 1 ) );
            $logger->warn( "adjusting this value to be " . ( $#lines + 1 ) );
            $minLine = $#lines + 1;
        }

        # maxline > number of lines needs addressing
        if ( $maxLine - 1 > $#lines ) {
            $logger->warn(
                "*--lines specified with max line $maxLine which is *greater than* the number of lines in file: "
                    . ( $#lines + 1 ) );
            $logger->warn( "adjusting this value to be " . ( $#lines + 1 ) );
            $maxLine = $#lines + 1;
        }

        # either store the negation, or not
        if ($negationMode) {
            $negationMinMaxStorage{$minLine} = $maxLine;
        }
        else {
            $minMaxStorage{$minLine} = $maxLine;
        }
        $logger->info("min line: $minLine, max line: $maxLine");
    }

    # only proceed if we have a valid line range
    if ( ( keys %minMaxStorage ) < 1 and ( keys %negationMinMaxStorage ) < 1 ) {
        $logger->warn("*--lines not specified with valid range: $switch{lines}");
        $logger->warn("entire body will be indented, and ignoring $switch{lines}");
        $switch{lines} = 0;
        ${$self}{body} = join( "", @lines );
        return;
    }

    # we need to perform the token check here
    ${$self}{body} = join( "", @lines );
    $self->token_check;
    ${$self}{body} = q();

    # negated line ranges
    if ( keys %negationMinMaxStorage >= 1 ) {
        @NOTindentLineRange = &lines_sort_and_combine_line_range( \%negationMinMaxStorage );

        $logger->info("*negation line range summary: ");
        $logger->info( "the number of NEGATION line ranges: " . ( ( $#NOTindentLineRange + 1 ) / 2 ) );
        $logger->info("the *sorted* NEGATION line ranges are in the form MIN-MAX: ");
        for ( my $index = 0; $index < ( ( $#NOTindentLineRange + 1 ) / 2 ); $index++ ) {
            $logger->info( join( "-", @NOTindentLineRange[ 2 * $index .. 2 * $index + 1 ] ) );

            if ( $index == 0 and $NOTindentLineRange[ 2 * $index ] > 1 ) {
                $minMaxStorage{1} = $NOTindentLineRange[ 2 * $index ] - 1;
            }
            elsif ( $index > 0 ) {
                $minMaxStorage{ $NOTindentLineRange[ 2 * $index - 1 ] + 1 } = $NOTindentLineRange[ 2 * $index ] - 1;
            }
        }

        # final range
        if ( $NOTindentLineRange[-1] < $#lines ) {
            $minMaxStorage{ $NOTindentLineRange[-1] + 1 } = $#lines + 1;
        }
    }

    @indentLineRange = &lines_sort_and_combine_line_range( \%minMaxStorage ) if ( keys %minMaxStorage >= 1 );

    $logger->info("*line range summary: ");
    $logger->info( "the number of indent line ranges: " . ( ( $#indentLineRange + 1 ) / 2 ) );
    $logger->info("the *sorted* line ranges are in the form MIN-MAX: ");
    for ( my $index = 0; $index < ( ( $#indentLineRange + 1 ) / 2 ); $index++ ) {
        $logger->info( join( "-", @indentLineRange[ 2 * $index .. 2 * $index + 1 ] ) );
    }

    my $startLine = 0;

    # now that we have the line range, we can sort arrange the body
    while ( $#indentLineRange > 0 ) {
        my $minLine = shift(@indentLineRange);
        my $maxLine = shift(@indentLineRange);

        # perl arrays start at 0
        $minLine--;
        $maxLine--;

        $self->lines_verbatim_create_line_block( \@lines, $startLine, $minLine - 1 ) unless ( $minLine == 0 );

        ${$self}{body} .= join( "", @lines[ $minLine .. $maxLine ] );

        $startLine = $maxLine + 1;
    }

    # final line range
    $self->lines_verbatim_create_line_block( \@lines, $startLine, $#lines ) if ( $startLine <= $#lines );
    return;
}

sub lines_sort_and_combine_line_range {

    my %minMaxStorage = %{ $_[0] };
    #
    #     --lines 8-10,4-5,1-2
    #
    # needs to be interpreted as
    #
    #     --lines 1-2,4-5,8-10,
    #
    # sort the line ranges by the *minimum* value, the associated
    # maximum values will be arranged after this
    my @indentLineRange = sort { $a <=> $b } keys(%minMaxStorage);

    my @justMinimumValues = @indentLineRange;
    for ( my $index = 0; $index <= $#justMinimumValues; $index++ ) {
        splice( @indentLineRange, 2 * $index + 1, 0, $minMaxStorage{ $justMinimumValues[$index] } );
    }

    for ( my $index = 1; $index < ( ( $#indentLineRange + 1 ) / 2 ); $index++ ) {
        my $currentMin  = $indentLineRange[ 2 * $index ];
        my $currentMax  = $indentLineRange[ 2 * $index + 1 ];
        my $previousMax = $indentLineRange[ 2 * $index - 1 ];
        my $previousMin = $indentLineRange[ 2 * $index - 2 ];

        if ( ( $currentMin - 1 ) <= $previousMax and ( $currentMax > $previousMax ) ) {

            # overlapping line ranges, for example
            #
            #     --lines 3-5,4-10
            #
            # needs to be interpreted as
            #
            #     --lines 3-10
            #
            $logger->info("overlapping line range found");
            $logger->info( "line ranges (before): " . join( ", ", @indentLineRange ) );
            splice( @indentLineRange, 2 * $index - 1, 2 );
            $logger->info( "line ranges (after): " . join( ", ", @indentLineRange ) );

            # reset index so that loop starts again
            $index = 0;
        }
        elsif ( ( $currentMin - 1 ) <= $previousMax and ( $currentMax <= $previousMax ) ) {

            # overlapping line ranges, for example
            #
            #     --lines 3-7,4-6
            #
            # needs to be interpreted as
            #
            #     --lines 3-7
            #
            $logger->info("overlapping line range found");
            $logger->info( "line ranges (before): " . join( ", ", @indentLineRange ) );
            splice( @indentLineRange, 2 * $index, 2 );
            $logger->info( "line ranges (after): " . join( ", ", @indentLineRange ) );

            # reset index so that loop starts again
            $index = 0;
        }
    }

    return @indentLineRange;
}

sub lines_verbatim_create_line_block {
    my $self       = shift;
    my @lines      = @{ $_[0] };
    my $startLine  = $_[1];
    my $finishLine = $_[2];

    my $verbBody = join( "", @lines[ $startLine .. $finishLine ] );
    $verbBody =~ s/\R?$//s;

    # create a new Verbatim object
    my $noIndentBlockObj = LatexIndent::Verbatim->new(
        begin                    => q(),
        body                     => $verbBody,
        end                      => q(),
        name                     => "line-switch-verbatim-protection",
        type                     => "linesprotect",
        modifyLineBreaksYamlName => "lines-not-to-be-indented",
    );

    # give unique id
    $noIndentBlockObj->create_unique_id;

    # verbatim children go in special hash
    $verbatimStorage{ ${$noIndentBlockObj}{id} } = $noIndentBlockObj;

    # remove the environment block, and replace with unique ID
    ${$self}{body} .= ${$noIndentBlockObj}{id} . "\n";

    return;
}

1;
