package LatexIndent::Special;

#	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::Tokens           qw/%tokens/;
use LatexIndent::TrailingComments qw/$trailingCommentRegExp/;
use LatexIndent::GetYamlSettings  qw/%mainSettings/;
use LatexIndent::Switches         qw/$is_t_switch_active $is_tt_switch_active/;
use LatexIndent::LogFile          qw/$logger/;
use LatexIndent::IfElseFi;
use Data::Dumper;
our @ISA = "LatexIndent::Document";    # class inheritance, Programming Perl, pg 321
our @EXPORT_OK
    = qw/find_special construct_special_begin $specialBeginAndBracesBracketsBasicRegExp $specialBeginBasicRegExp/;
our $specialCounter;
our $specialBegins           = q();
our $specialAllMatchesRegExp = q();
our %individualSpecialRegExps;
our $specialBeginAndBracesBracketsBasicRegExp;
our $specialBeginBasicRegExp;

sub construct_special_begin {
    my $self = shift;

    $logger->trace("*Constructing specialBeginEnd regex (see specialBeginEnd)") if $is_t_switch_active;

    # put together a list of the begin terms in special
    while ( my ( $specialName, $BeginEnd ) = each %{ $mainSettings{specialBeginEnd} } ) {
        if ( ref($BeginEnd) eq "HASH" ) {
            if ( not defined ${$BeginEnd}{lookForThis} ) {
                ${$BeginEnd}{lookForThis} = 1;
                ${ ${ $mainSettings{specialBeginEnd} }{$specialName} }{lookForThis} = 1;
                $logger->trace("setting lookForThis:1 for $specialName (lookForThis not specified)")
                    if $is_t_switch_active;
            }

            # only append the regexps if lookForThis is 1
            $specialBegins .= ( $specialBegins eq "" ? q() : "|" ) . ${$BeginEnd}{begin}
                if ( ${$BeginEnd}{lookForThis} =~ m/\d/s and ${$BeginEnd}{lookForThis} == 1 );
        }
    }

    # put together a list of the begin terms in special
    while ( my ( $specialName, $BeginEnd ) = each %{ $mainSettings{specialBeginEnd} } ) {

        # only append the regexps if lookForThis is 1
        if ( ref($BeginEnd) eq "HASH" ) {
            if ( ${$BeginEnd}{lookForThis} =~ m/\d/s and ${$BeginEnd}{lookForThis} == 0 ) {
                $logger->trace("The specialBeginEnd regexps won't include anything from $specialName (lookForThis: 0)")
                    if $is_t_switch_active;
                next;
            }
        }
        else {
            next;
        }

        # body of specialBeginEnd
        my $specialBodyRegEx = q();
        if ( defined ${$BeginEnd}{body} ) {
            $specialBodyRegEx = qr/${$BeginEnd}{body}/;
        }
        else {
            $specialBodyRegEx = qr/(?:                        # cluster-only (), don't capture 
                                        (?!             
                                            (?:$specialBegins) # cluster-only (), don't capture
                                        ).                     # any character, but not anything in $specialBegins
                                  )*?/sx;
        }

        # the overall regexp
        $specialAllMatchesRegExp .= ( $specialAllMatchesRegExp eq "" ? q() : "|" ) . qr/
                                  ${$BeginEnd}{begin}
                                  $specialBodyRegEx 
                                  ${$BeginEnd}{end}
                           /sx;

        # store the individual special regexp
        $individualSpecialRegExps{$specialName} = qr/
                                (
                                    ${$BeginEnd}{begin}
                                    \h*
                                    (\R*)?
                                )
                                (
                                   $specialBodyRegEx 
                                   (\R*)?
                                )                       
                                (
                                  ${$BeginEnd}{end}
                                )
                                (\h*)
                                (\R)?
                             /sx

    }

    # move $$ to the beginning
    if ( $specialBegins =~ m/\|\\\$\\\$/ ) {
        $specialBegins =~ s/\|(\\\$\\\$)//;
        $specialBegins = $1 . "|" . $specialBegins;
    }

    # info to the log file
    $logger->trace("*The special beginnings regexp is: (see specialBeginEnd)") if $is_tt_switch_active;
    $logger->trace($specialBegins)                                             if $is_tt_switch_active;

    # overall special regexp
    $logger->trace("*The overall special regexp is: (see specialBeginEnd)") if $is_tt_switch_active;
    $logger->trace($specialAllMatchesRegExp)                                if $is_tt_switch_active;

    # basic special begin regexp
    $specialBeginBasicRegExp                  = qr/$specialBegins/;
    $specialBeginAndBracesBracketsBasicRegExp = $specialBegins . "|\\{|\\[";
    $specialBeginAndBracesBracketsBasicRegExp = qr/$specialBeginAndBracesBracketsBasicRegExp/;
}

sub find_special {
    my $self = shift;

    # no point carrying on if the list of specials is empty
    return if ( $specialBegins eq "" );

    # otherwise loop through the special begin/end
    $logger->trace("*Searching ${$self}{name} for special begin/end (see specialBeginEnd)") if $is_t_switch_active;
    $logger->trace( Dumper( \%{ $mainSettings{specialBeginEnd} } ) )                        if $is_tt_switch_active;

    # keep looping as long as there is a special match of some kind
    while ( ${$self}{body} =~ m/$specialAllMatchesRegExp/sx ) {

        # loop through each special match
        while ( my ( $specialName, $BeginEnd ) = each %{ $mainSettings{specialBeginEnd} } ) {

            # log file
            if (    ( ref($BeginEnd) eq "HASH" )
                and ${$BeginEnd}{lookForThis} =~ m/\d/s
                and ${$BeginEnd}{lookForThis} == 1 )
            {
                $logger->trace("Looking for $specialName") if $is_t_switch_active;
            }
            else {
                $logger->trace("Not looking for $specialName (see lookForThis)")
                    if ( $is_t_switch_active and ( ref($BeginEnd) eq "HASH" ) );
                next;
            }

            # the regexp
            my $specialRegExp = $individualSpecialRegExps{$specialName};
            $logger->trace("$specialName regexp: \n$specialRegExp") if $is_tt_switch_active;

            while ( ${$self}{body} =~ m/$specialRegExp(\h*)($trailingCommentRegExp)?/ ) {

                # global substitution
                ${$self}{body} =~ s/
                                    $specialRegExp(\h*)($trailingCommentRegExp)?
                                   /
                                    # create a new special object
                                    my $specialObject = LatexIndent::Special->new(begin=>$1,
                                                                            body=>$3,
                                                                            end=>$5,
                                                                            name=>$specialName,
                                                                            linebreaksAtEnd=>{
                                                                              begin=>$2?1:0,
                                                                              body=>$4?1:0,
                                                                              end=>$7?1:0,
                                                                            },
                                                                            aliases=>{
                                                                              # begin statements
                                                                              BeginStartsOnOwnLine=>"SpecialBeginStartsOnOwnLine",
                                                                              # body statements
                                                                              BodyStartsOnOwnLine=>"SpecialBodyStartsOnOwnLine",
                                                                              # end statements
                                                                              EndStartsOnOwnLine=>"SpecialEndStartsOnOwnLine",
                                                                              # after end statements
                                                                              EndFinishesWithLineBreak=>"SpecialEndFinishesWithLineBreak",
                                                                            },
                                                                            modifyLineBreaksYamlName=>"specialBeginEnd",
                                                                            endImmediatelyFollowedByComment=>$7?0:($9?1:0),
                                                                            horizontalTrailingSpace=>$6?$6:q(),
                                                                          );

                                    # log file output
                                    $logger->trace("*Special found: $specialName") if $is_t_switch_active;

                                    # the settings and storage of most objects has a lot in common
                                    $self->get_settings_and_store_new_object($specialObject);
                                    ${@{${$self}{children}}[-1]}{replacementText}.($8?$8:q()).($9?$9:q());
                                    /xseg;

                $self->wrap_up_tasks;
            }
        }
    }
}

sub tasks_particular_to_each_object {
    my $self = shift;

    if ( defined ${ ${ $mainSettings{specialBeginEnd} }{ ${$self}{name} } }{middle} ) {
        $logger->trace("middle specified for ${$self}{name} (see specialBeginEnd -> ${$self}{name} -> middle)")
            if $is_t_switch_active;

        # initiate the middle regexp
        my $specialMiddle = q();

        # we can specify middle as either an array or a hash
        if ( ref( ${ ${ $mainSettings{specialBeginEnd} }{ ${$self}{name} } }{middle} ) eq "ARRAY" ) {
            $logger->trace("looping through middle array for ${$self}{name}") if $is_t_switch_active;
            foreach ( @{ ${ ${ $mainSettings{specialBeginEnd} }{ ${$self}{name} } }{middle} } ) {
                $specialMiddle .= ( $specialMiddle eq "" ? q() : "|" ) . $_;
            }
            $specialMiddle = qr/$specialMiddle/;
        }
        else {
            $specialMiddle = qr/${${$mainSettings{specialBeginEnd}}{${$self}{name}}}{middle}/;
        }

        $logger->trace("overall middle regexp for ${$self}{name}: $specialMiddle") if $is_t_switch_active;

        # store the middle regexp for later
        ${$self}{middleRegExp} = $specialMiddle;

        # check for existence of a 'middle' statement, and associated line break information
        $self->check_for_else_statement(

            # else name regexp
            elseNameRegExp => $specialMiddle,

            # else statements name
            ElseStartsOnOwnLine => "SpecialMiddleStartsOnOwnLine",

            # end statements
            ElseFinishesWithLineBreak => "SpecialMiddleFinishesWithLineBreak",

            # for the YAML settings storage
            storageNameAppend => "middle",

            # logfile information
            logName => "special middle",
        );

    }

    $self->find_special;

    return unless ( ${ $mainSettings{specialBeginEnd} }{specialBeforeCommand} );

    # lookForAlignDelims: lookForChildCodeBlocks set to 0 means no child objects searched for
    #   see: test-cases/alignment/issue-308-special.tex
    #
    if ( defined ${$self}{lookForChildCodeBlocks} and !${$self}{lookForChildCodeBlocks} ) {
        $logger->trace(
            "lookForAlignDelims: lookForChildCodeBlocks set to 0, so child objects will *NOT* be searched for")
            if ($is_t_switch_active);
        return;
    }

    # search for commands with arguments
    $self->find_commands_or_key_equals_values_braces;

    # search for arguments
    $self->find_opt_mand_arguments;

    # search for ifElseFi blocks
    $self->find_ifelsefi;

}

sub post_indentation_check {

    # needed to remove leading horizontal space before \else
    my $self = shift;

    return unless ( defined ${ ${ $mainSettings{specialBeginEnd} }{ ${$self}{name} } }{middle} );

    $logger->trace("post indentation check for ${$self}{name} to account for middle") if $is_t_switch_active;

    # loop through \else and \or
    foreach ( { regExp => ${$self}{middleRegExp} } ) {
        my %else = %{$_};
        if ( ${$self}{body} =~ m/^\h*$else{regExp}/sm
            and !( ${$self}{body} =~ m/^\h*$else{regExp}/s and ${$self}{linebreaksAtEnd}{begin} == 0 ) )
        {
            $logger->trace(
                "*Adding surrounding indentation to $else{regExp} statement(s) ('${$self}{surroundingIndentation}')")
                if $is_t_switch_active;
            ${$self}{body} =~ s/^\h*($else{regExp})/${$self}{surroundingIndentation}$1/smg;
        }
    }
    return;
}

sub create_unique_id {
    my $self = shift;

    $specialCounter++;

    ${$self}{id} = "$tokens{specialBeginEnd}$specialCounter";
    return;
}

1;
