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
#
#	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/%switches/;
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
    $switches{lines} =~ s/\h//sg;

    $logger->info("*-n,--lines switch is active, operating on lines $switches{lines}"); 
    $logger->info("number of lines in file: ".($#lines+1));
    $logger->info("*interpretting $switches{lines}"); 
    
    my @lineRanges = split(/,/,$switches{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: $switches{lines}");
      $logger->warn("entire body will be indented, and ignoring $switches{lines}");
      $switches{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 interpretted 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 interpretted 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 interpretted 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;
