package LatexIndent::Indent;
#	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 LatexIndent::Tokens qw/%tokens/;
use LatexIndent::Switches qw/$is_m_switch_active $is_t_switch_active $is_tt_switch_active/;
use LatexIndent::HiddenChildren qw/%familyTree/;
use LatexIndent::GetYamlSettings qw/%masterSettings/;
use LatexIndent::LogFile qw/$logger/;
use Text::Tabs;
use Data::Dumper;
use Exporter qw/import/;
our @EXPORT_OK = qw/indent wrap_up_statement determine_total_indentation indent_begin indent_body indent_end_statement final_indentation_check push_family_tree_to_indent get_surrounding_indentation indent_children_recursively check_for_blank_lines_at_beginning put_blank_lines_back_in_at_beginning add_surrounding_indentation_to_begin_statement post_indentation_check/;
our %familyTree;

sub indent{
    my $self = shift;

    # determine the surrounding and current indentation
    $self->determine_total_indentation;

    # indent the begin statement
    $self->indent_begin;

    # indent the body
    $self->indent_body;

    # indent the end statement
    $self->indent_end_statement;

    # output the completed object to the log file
    $logger->trace("Complete indented object (${$self}{name}) after indentation:\n${$self}{begin}${$self}{body}${$self}{end}") if $is_tt_switch_active;

    # wrap-up statement
    $self->wrap_up_statement;
    return $self;
}

sub wrap_up_statement{
    my $self = shift;
    $logger->trace("*Finished indenting ${$self}{name}") if $is_t_switch_active;
    return $self;
  }

sub determine_total_indentation{
    my $self = shift;

    # calculate and grab the surrounding indentation
    $self->get_surrounding_indentation;

    # logfile information
    my $surroundingIndentation = ${$self}{surroundingIndentation};
    $logger->trace("indenting object ${$self}{name}") if($is_t_switch_active);
    (my $during = $surroundingIndentation) =~ s/\t/TAB/g;
    $logger->trace("indentation *surrounding* object: '$during'") if($is_t_switch_active);
    ($during = ${$self}{indentation}) =~ s/\t/TAB/g;
    $logger->trace("indentation *of* object: '$during'") if($is_t_switch_active);
    ($during = $surroundingIndentation.${$self}{indentation}) =~ s/\t/TAB/g;
    $logger->trace("*total* indentation to be added: '$during'") if($is_t_switch_active);

    # form the total indentation of the object
    ${$self}{indentation} = $surroundingIndentation.${$self}{indentation};

}

sub get_surrounding_indentation{
    my $self = shift;

    my $surroundingIndentation = q();

    if($familyTree{${$self}{id}}){
        $logger->trace("Adopted ancestors found!") if($is_t_switch_active);
        foreach(@{${$familyTree{${$self}{id}}}{ancestors}}){
            if(${$_}{type} eq "adopted"){
                my $newAncestorId = ${$_}{ancestorID};
                $logger->trace("ancestor ID: $newAncestorId, adding indentation of $newAncestorId to surroundingIndentation of ${$self}{id}") if($is_t_switch_active);
                $surroundingIndentation .= ref(${$_}{ancestorIndentation}) eq 'SCALAR'
                                                    ?
                                            (${${$_}{ancestorIndentation}}?${${$_}{ancestorIndentation}}:q())
                                                    :
                                            (${$_}{ancestorIndentation}?${$_}{ancestorIndentation}:q());
            }
        }
    }
    ${$self}{surroundingIndentation} = $surroundingIndentation;

}

sub indent_begin{
    # for most objects, the begin statement is just one line, but there are exceptions, e.g KeyEqualsValuesBraces
    return;
}

sub indent_body{
    my $self = shift;

    # grab the indentation of the object
    my $indentation = ${$self}{indentation};

    # output to the logfile
    $logger->trace("Body (${$self}{name}) before indentation:\n${$self}{body}") if $is_tt_switch_active;

    # last minute check for modified bodyLineBreaks
    $self->count_body_line_breaks if $is_m_switch_active;

    # some objects need to check for blank line tokens at the beginning
    $self->check_for_blank_lines_at_beginning if $is_m_switch_active; 

    # some objects can format their body to align at the & character
    $self->align_at_ampersand if ${$self}{lookForAlignDelims};

    # possibly remove paragraph line breaks
    $self->remove_paragraph_line_breaks if ($is_m_switch_active and ${$self}{removeParagraphLineBreaks} and !${$masterSettings{modifyLineBreaks}{removeParagraphLineBreaks}}{beforeTextWrap});

    # body indendation
    if(${$self}{linebreaksAtEnd}{begin}==1){
        if(${$self}{body} =~ m/^\h*$/s){
            $logger->trace("Body of ${$self}{name} is empty, not applying indentation") if $is_t_switch_active;
        } else {
            # put any existing horizontal space after the current indentation
            $logger->trace("Entire body of ${$self}{name} receives indendentation") if $is_t_switch_active;
            ${$self}{body} =~ s/^(\h*)/$indentation$1/mg;  # add indentation
        }
    } elsif(${$self}{linebreaksAtEnd}{begin}==0 and ${$self}{bodyLineBreaks}>0) {
        if(${$self}{body} =~ m/
                            (.*?)      # content of first line
                            \R         # first line break
                            (.*$)      # rest of body
                            /sx){
            my $bodyFirstLine = $1;
            my $remainingBody = $2;
            $logger->trace("first line of body: $bodyFirstLine") if $is_tt_switch_active;
            $logger->trace("remaining body (before indentation):\n'$remainingBody'") if($is_tt_switch_active);
    
            # add the indentation to all the body except first line
            $remainingBody =~ s/^/$indentation/mg unless($remainingBody eq '');  # add indentation
            $logger->trace("remaining body (after indentation):\n$remainingBody'") if($is_tt_switch_active);
    
            # put the body back together
            ${$self}{body} = $bodyFirstLine."\n".$remainingBody; 
        }
    }

    # some objects need a post-indentation check, e.g ifElseFi
    $self->post_indentation_check;
    # if the routine check_for_blank_lines_at_beginning has been called, then the following routine
    # puts blank line tokens back in 
    $self->put_blank_lines_back_in_at_beginning if $is_m_switch_active; 

    # the final linebreak can be modified by a child object; see test-cases/commands/figureValign-mod5.tex, for example
    if($is_m_switch_active and defined ${$self}{linebreaksAtEnd}{body} and ${$self}{linebreaksAtEnd}{body}==1 and ${$self}{body} !~ m/\R$/ and ${$self}{body} ne ''){
        $logger->trace("Adding a linebreak at end of body for ${$self}{name} to contain a linebreak at the end (linebreaksAtEnd is 1, but there isn't currently a linebreak)") if($is_t_switch_active);
        ${$self}{body} .= "\n";
    }

    # output to the logfile
    $logger->trace("Body (${$self}{name}) after indentation:\n${$self}{body}") if $is_tt_switch_active;
    return $self;
}

sub post_indentation_check{
    return;
}

sub check_for_blank_lines_at_beginning{
    # some objects need this routine
    return;
}

sub put_blank_lines_back_in_at_beginning{
    # some objects need this routine
    return;
}

sub indent_end_statement{
    my $self = shift;
    my $surroundingIndentation = (${$self}{surroundingIndentation} and $familyTree{${$self}{id}})
                                            ?
                                 (ref(${$self}{surroundingIndentation}) eq 'SCALAR'?${${$self}{surroundingIndentation}}:${$self}{surroundingIndentation})
                                            :q();

    # end{statement} indentation, e.g \end{environment}, \fi, }, etc
    if(${$self}{linebreaksAtEnd}{body}){
        ${$self}{end} =~ s/^\h*/$surroundingIndentation/mg;  # add indentation
        $logger->trace("Adding surrounding indentation to ${$self}{end} (${$self}{name}: '$surroundingIndentation')") if($is_t_switch_active);
     }
    return $self;
}

sub final_indentation_check{
    # problem:
    #       if a tab is appended to spaces, it will look different 
    #       from spaces appended to tabs (see test-cases/items/spaces-and-tabs.tex)
    # solution:
    #       move all of the tabs to the beginning of ${$self}{indentation}
    # notes;
    #       this came to light when studying test-cases/items/items1.tex

    my $self = shift;

    my $indentation;
    my $numberOfTABS; 
    my $after;
    ${$self}{body} =~ s/
                        ^((\h*|\t*)((\h+)(\t+))+)
                        /   
                        # fix the indentation
                        $indentation = $1;

                        # count the number of tabs
                        $numberOfTABS = () = $indentation=~ \/\t\/g;
                        $logger->trace("Number of tabs: $numberOfTABS") if($is_t_switch_active);

                        # log the after
                        ($after = $indentation) =~ s|\t||g;
                        $after = "TAB"x$numberOfTABS.$after;
                        $logger->trace("Indentation after: '$after'") if($is_t_switch_active);
                        ($indentation = $after) =~s|TAB|\t|g;

                        $indentation;
                       /xsmeg;

    return unless($masterSettings{maximumIndentation} =~ m/^\h+$/);

    # maximum indentation check
    $logger->trace("*Maximum indentation check") if($is_t_switch_active);

    # replace any leading tabs with spaces, and update the body
    my @expanded_lines = expand(${$self}{body});
    ${$self}{body} = join("",@expanded_lines);

    # grab the maximum indentation
    my $maximumIndentation = $masterSettings{maximumIndentation};
    my $maximumIndentationLength = length($maximumIndentation)+1;

    # replace any leading space that is greater than the 
    # specified maximum indentation with the maximum indentation
    ${$self}{body} =~ s/^\h{$maximumIndentationLength,}/$maximumIndentation/smg;
}

sub indent_children_recursively{
    my $self = shift;

    unless(defined ${$self}{children}) {
        $logger->trace("No child objects (${$self}{name})") if $is_t_switch_active;
        return;
    }

    $logger->trace('Pre-processed body:') if $is_tt_switch_active;
    $logger->trace(${$self}{body}) if($is_tt_switch_active);

    # send the children through this indentation routine recursively
    if(defined ${$self}{children}){
        foreach my $child (@{${$self}{children}}){
            $logger->trace("Indenting child objects on ${$child}{name}") if $is_t_switch_active;
            $child->indent_children_recursively;
        }
    } 

    $logger->trace("*Replacing ids with begin, body, and end statements:") if $is_t_switch_active;

    # loop through document children hash
    while( scalar (@{${$self}{children}}) > 0 ){
          my $index = 0;
          # we work through the array *in order*
          foreach my $child (@{${$self}{children}}){
            $logger->trace("Searching ${$self}{name} for ${$child}{id}...") if $is_t_switch_active;
            if(${$self}{body} =~ m/${$child}{idRegExp}/s){
                # we only care if id is first non-white space character 
                # and if followed by line break 
                # if m switch is active 
                my $IDFirstNonWhiteSpaceCharacter = 0;
                my $IDFollowedImmediatelyByLineBreak = 0;

                # update the above two, if necessary
                if ($is_m_switch_active){
                    $IDFirstNonWhiteSpaceCharacter = (${$self}{body} =~ m/^${$child}{idRegExp}/m 
                                                            or 
                                                         ${$self}{body} =~ m/^\h\h*${$child}{idRegExp}/m
                                                        ) ?1:0;
                    $IDFollowedImmediatelyByLineBreak = (${$self}{body} =~ m/${$child}{idRegExp}\h*\R+/m) ?1:0;
                    ${$child}{IDFollowedImmediatelyByLineBreak} = $IDFollowedImmediatelyByLineBreak; 
               }

                # log file info
                $logger->trace("${$child}{id} found!") if($is_t_switch_active);
                $logger->trace("*Indenting  ${$child}{name} (id: ${$child}{id})") if $is_t_switch_active;
                $logger->trace("looking up indentation scheme for ${$child}{name}") if($is_t_switch_active);

                # line break checks *after* <end statement>
                if (defined ${$child}{EndFinishesWithLineBreak}
                    and ${$child}{EndFinishesWithLineBreak}==-1 
                    and $IDFollowedImmediatelyByLineBreak) {
                    # remove line break *after* <end statement>, if appropriate
                    my $EndStringLogFile = ${$child}{aliases}{EndFinishesWithLineBreak}||"EndFinishesWithLineBreak";
                    $logger->trace("Removing linebreak after ${$child}{end} (see $EndStringLogFile)") if $is_t_switch_active;
                    ${$self}{body} =~ s/${$child}{idRegExp}(\h*)?(\R|\h)*/${$child}{id}$1/s;
                    ${$child}{linebreaksAtEnd}{end} = 0;
                }

                # perform indentation
                $child->indent;

                # surrounding indentation is now up to date
                my $surroundingIndentation = (${$child}{surroundingIndentation} and ${$child}{hiddenChildYesNo})
                                                        ?
                                             (ref(${$child}{surroundingIndentation}) eq 'SCALAR'?${${$child}{surroundingIndentation}}:${$child}{surroundingIndentation})
                                                        :q();

                # line break checks before <begin statement>
                if(defined ${$child}{BeginStartsOnOwnLine}){
                    my $BeginStringLogFile = ${$child}{aliases}{BeginStartsOnOwnLine}||"BeginStartsOnOwnLine";

                    # 
                    # Blank line poly-switch notes (==4)
                    #
                    # when BeginStartsOnOwnLine=4 we adopt the following approach:
                    #   temporarily change BeginStartsOnOwnLine to -1, make adjustments
                    #   temporarily change BeginStartsOnOwnLine to 3, make adjustments
                    #
                    # we use an array, @polySwitchValues to facilitate this
                    my @polySwitchValues = (${$child}{BeginStartsOnOwnLine}==4)?(-1,3):(${$child}{BeginStartsOnOwnLine});

                    foreach(@polySwitchValues){
                        # if BeginStartsOnOwnLine is 4, then we hack 
                        #       $IDFirstNonWhiteSpaceCharacter 
                        # to be 0 on the second time through (poly-switch set to 3)
                        $IDFirstNonWhiteSpaceCharacter = 0 if (${$child}{BeginStartsOnOwnLine}==4 and $_==3);

                        # if the child ID is not the first character and BeginStartsOnOwnLine>=1 
                        # then we will need to add a line break (==1), a comment (==2) or another blank line (==3)
                        if($_>=1 and !$IDFirstNonWhiteSpaceCharacter){
                            # by default, assume that no trailing comment token is needed
                            my $trailingCharacterToken = q();
                            if($_==2){
                                $logger->trace("Removing space immediately before ${$child}{id}, in preparation for adding % ($BeginStringLogFile == 2)") if $is_t_switch_active;
                                ${$self}{body} =~ s/\h*${$child}{idRegExp}/${$child}{id}/s;
                                $logger->trace("Adding a % at the end of the line that ${$child}{begin} is on, then a linebreak ($BeginStringLogFile == 2)") if $is_t_switch_active;
                                $trailingCharacterToken = "%".$self->add_comment_symbol;
                            } elsif ($_==3){
                                $logger->trace("Adding a blank line at the end of the line that ${$child}{begin} is on, then a linebreak ($BeginStringLogFile == 3)") if $is_t_switch_active;
                                $trailingCharacterToken = "\n".(${$masterSettings{modifyLineBreaks}}{preserveBlankLines}?$tokens{blanklines}:q());
                            } else {
                                $logger->trace("Adding a linebreak at the beginning of ${$child}{begin} (see $BeginStringLogFile)") if $is_t_switch_active;
                            }

                            # the trailing comment/linebreak magic
                            ${$child}{begin} = "$trailingCharacterToken\n".${$child}{begin};
                            $child->add_surrounding_indentation_to_begin_statement;

                            # remove surrounding indentation ahead of %
                            ${$child}{begin} =~ s/^(\h*)%/%/ if($_==2);
                        } elsif ($_==-1 and $IDFirstNonWhiteSpaceCharacter){
                            # finally, if BeginStartsOnOwnLine == -1 then we might need to *remove* a blank line(s)
                            # important to check we don't move the begin statement next to a blank-line-token
                            my $blankLineToken = $tokens{blanklines};
                            if(${$self}{body} !~ m/$blankLineToken\R*\h*${$child}{idRegExp}/s){
                                $logger->trace("Removing linebreak before ${$child}{begin} (see $BeginStringLogFile in ${$child}{modifyLineBreaksYamlName} YAML)") if $is_t_switch_active;
                                ${$self}{body} =~ s/(\h*)(?:\R*|\h*)+${$child}{idRegExp}/$1${$child}{id}/s;
                            } else {
                                $logger->trace("Not removing linebreak ahead of ${$child}{begin}, as blank-line-token present (see preserveBlankLines)") if $is_t_switch_active;
                            }
                        }
                     }
                }

                $logger->trace(Dumper(\%{$child})) if($is_tt_switch_active);

                # replace ids with body
                ${$self}{body} =~ s/${$child}{idRegExp}/${$child}{begin}${$child}{body}${$child}{end}/;

                # log file info
                $logger->trace("Body (${$self}{name}) now looks like:") if $is_tt_switch_active;
                $logger->trace(${$self}{body}) if($is_tt_switch_active);

                # remove element from array: http://stackoverflow.com/questions/174292/what-is-the-best-way-to-delete-a-value-from-an-array-in-perl
                splice(@{${$self}{children}}, $index, 1);

                # output to the log file
                $logger->trace("deleted child key ${$child}{name} (parent is: ${$self}{name})") if $is_t_switch_active;

                # restart the loop, as the size of the array has changed
                last;
              } else {
                $logger->trace("${$child}{id} not found") if($is_t_switch_active);
              }

              # increment the loop counter
              $index++;
            }
    }

    # logfile info
    $logger->trace("${$self}{name} has this many children:") if $is_tt_switch_active;
    $logger->trace(scalar @{${$self}{children}}) if $is_tt_switch_active;
    $logger->trace("Post-processed body (${$self}{name}):") if($is_tt_switch_active);
    $logger->trace(${$self}{body}) if($is_tt_switch_active);

}

sub add_surrounding_indentation_to_begin_statement{
    # almost all of the objects add surrounding indentation to the 'begin' statements, 
    # but some (e.g HEADING) have their own method
    my $self = shift;

    my $surroundingIndentation = ${$self}{surroundingIndentation};
    ${$self}{begin} =~ s/^(\h*)?/$surroundingIndentation/mg;  # add indentation

}

1;
