package LatexIndent::Document;

#	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 Data::Dumper;
use File::Basename;    # to get the filename and directory path

use open ':std', ':encoding(UTF-8)';
use Encode qw/decode/;

# gain access to subroutines in the following modules
use LatexIndent::Switches
    qw/store_switches %switch $is_m_switch_active $is_t_switch_active $is_tt_switch_active $is_r_switch_active $is_rr_switch_active $is_rv_switch_active $is_check_switch_active/;
use LatexIndent::LogFile     qw/process_switches $logger/;
use LatexIndent::Logger      qw/@logFileLines/;
use LatexIndent::Check       qw/simple_diff/;
use LatexIndent::Lines       qw/lines_body_selected_lines lines_verbatim_create_line_block/;
use LatexIndent::Replacement qw/make_replacements/;
use LatexIndent::GetYamlSettings
    qw/yaml_obsolete_checks yaml_read_settings yaml_modify_line_breaks_settings yaml_get_indentation_settings_for_this_object yaml_poly_switch_get_every_or_custom_value yaml_get_indentation_information yaml_get_object_attribute_for_indentation_settings yaml_alignment_at_ampersand_settings %mainSetting yaml_get_alignment_at_ampersand_from_parent/;
use LatexIndent::FileExtension       qw/file_extension_check/;
use LatexIndent::BackUpFileProcedure qw/create_back_up_file check_if_different/;
use LatexIndent::BlankLines          qw/protect_blank_lines unprotect_blank_lines condense_blank_lines/;
use LatexIndent::ModifyLineBreaks
    qw/_mlb_line_break_token_adjust _mlb_file_starts_with_line_break _mlb_begin_starts_on_own_line _mlb_body_starts_on_own_line _mlb_end_starts_on_own_line _mlb_end_finishes_with_line_break adjust_line_breaks_end_parent _mlb_verbatim _mlb_after_indentation_token_adjust mlb_PRE_indent_sentence_and_text_wrap mlb_POST_indent_sentence_and_text_wrap/;
use LatexIndent::Sentence qw/one_sentence_per_line mlb_one_sentence_per_line_indent one_sentence_per_line_store /;
use LatexIndent::Wrap     qw/text_wrap text_wrap_comment_blocks/;
use LatexIndent::TrailingComments
    qw/remove_trailing_comments put_trailing_comments_back_in add_comment_symbol construct_trailing_comment_regexp $alignMarkUpBlockPresent/;
use LatexIndent::HorizontalWhiteSpace qw/remove_trailing_whitespace remove_leading_space max_indentation_check/;
use LatexIndent::Tokens               qw/token_check %tokens/;
use LatexIndent::AlignmentAtAmpersand
    qw/align_at_ampersand _align_mark_down_block double_back_slash_else main_formatting individual_padding multicolumn_padding multicolumn_pre_check  multicolumn_post_check dont_measure hidden_child_cell_row_width hidden_child_row_width /;
use LatexIndent::DoubleBackSlash qw/dodge_double_backslash un_dodge_double_backslash/;

# code blocks
use LatexIndent::Verbatim
    qw/put_verbatim_back_in find_verbatim_environments find_noindent_block find_verbatim_commands  find_verbatim_special verbatim_common_tasks %verbatimStorage/;
use LatexIndent::Environment;
use LatexIndent::IfElseFi;
use LatexIndent::Arguments;
use LatexIndent::OptionalArgument;
use LatexIndent::MandatoryArgument;
use LatexIndent::Blocks qw/$braceBracketRegExpBasic _find_all_code_blocks _construct_code_blocks_regex/;
use LatexIndent::Command;
use LatexIndent::KeyEqualsValuesBraces;
use LatexIndent::NamedGroupingBracesBrackets;
use LatexIndent::UnNamedGroupingBracesBrackets;
use LatexIndent::Special;
use LatexIndent::Heading      qw/find_heading construct_headings_levels $allHeadingsRegexp after_heading_indentation/;
use LatexIndent::FileContents qw/find_file_contents_environments_and_preamble/;

use LatexIndent::UTF8CmdLineArgsFileOperation
    qw/copy_with_encode exist_with_encode open_with_encode  zero_with_encode read_yaml_with_encode/;
use utf8;

sub new {

    # Create new objects, with optional key/value pairs
    # passed as initializers.
    #
    # See Programming Perl, pg 319
    my $invocant = shift;
    my $class    = ref($invocant) || $invocant;
    my $self     = {@_};
    $logger->trace( ${ $mainSetting{logFilePreferences} }{showDecorationStartCodeBlockTrace} )
        if ${ $mainSetting{logFilePreferences} }{showDecorationStartCodeBlockTrace};
    bless( $self, $class );
    return $self;
}

sub latexindent {
    my $self      = shift;
    my @fileNames = @{ $_[0] };

    my $check_switch_status_across_files = 0;

    my $file_extension_status_across_files = 0;

    # one-time operations
    $self->store_switches;
    ${$self}{fileName} = $fileNames[0];
    $self->process_switches( \@fileNames );
    $self->yaml_read_settings;

    ${$self}{multipleFiles} = 1 if ( ( scalar(@fileNames) ) > 1 );

    my $fileCount = 0;

    # per-file operations
    foreach (@fileNames) {
        $fileCount++;
        if ( ( scalar(@fileNames) ) > 1 ) {
            $logger->info( "*Filename: $_ (" . $fileCount . " of " . ( scalar(@fileNames) ) . ")" );
        }
        ${$self}{fileName}       = $_;
        ${$self}{cruftDirectory} = $switch{cruftDirectory} || ( dirname ${$self}{fileName} );

        # file existence/extension checks
        my $file_existence = $self->file_extension_check;
        if ( $file_existence > 0 ) {
            $file_extension_status_across_files = $file_existence;
            next;
        }

        # overwrite and overwriteIfDifferent switches, per file
        ${$self}{overwrite}            = $switch{overwrite};
        ${$self}{overwriteIfDifferent} = $switch{overwriteIfDifferent};

        # the main operations
        $self->operate_on_file;

        # keep track of check status across files
        $check_switch_status_across_files = 1
            if ( $is_check_switch_active and ${$self}{originalBody} ne ${$self}{body} );
    }

    # check switch summary across multiple files
    if ( $is_check_switch_active and ( scalar(@fileNames) ) > 1 ) {
        if ($check_switch_status_across_files) {
            $logger->info("*check switch across multiple files: differences to report from at least one file");
        }
        else {
            $logger->info("*check switch across multiple files: no differences to report");
        }
    }

    # logging of existence check
    if ( $file_extension_status_across_files > 2 ) {
        $logger->warn("*at least one of the files you specified does not exist or could not be read");
    }

    # output the log file information
    $self->output_logfile();

    if ( $file_extension_status_across_files > 2 ) {
        exit($file_extension_status_across_files);
    }

    # check switch active, and file changed, gives different exit code
    if ($check_switch_status_across_files) {
        exit(1);
    }
}

sub operate_on_file {
    my $self = shift;

    $self->create_back_up_file;
    $self->token_check unless ( $switch{lines} );
    $self->make_replacements( when => "before" )                if ( $is_r_switch_active and !$is_rv_switch_active );
    $self->_mlb_file_starts_with_line_break( when => "before" ) if $is_m_switch_active;
    unless ($is_rr_switch_active) {
        $self->construct_trailing_comment_regexp;
        $self->_construct_code_blocks_regex;

        # ---------- verbatim -------------------
        $self->find_noindent_block;
        $self->find_verbatim_commands;
        $self->remove_trailing_comments;
        $self->find_verbatim_environments;
        $self->find_verbatim_special;
        $self->_mlb_verbatim( when => "beforeTextWrap" ) if $is_m_switch_active;

        # ---------- END verbatim -------------------
        $self->make_replacements( when => "before" ) if $is_rv_switch_active;
        $self->protect_blank_lines                   if $is_m_switch_active;
        $self->remove_trailing_whitespace( when => "before" );
        $self->find_file_contents_environments_and_preamble;
        $self->dodge_double_backslash;
        $self->remove_leading_space;

        # ---------- one sentence per line, text wrap (BEFORE) -------------------
        $self->mlb_PRE_indent_sentence_and_text_wrap if $is_m_switch_active;

        # ---------- marked-up alignment blocks: %*\begin{tabular}...%*\end{tabular} ----------
        $self->_align_mark_down_block if $alignMarkUpBlockPresent;

        # ---------- main code blocks  -------------------
        ${$self}{body} = _find_all_code_blocks( ${$self}{body}, "" );
        ${$self}{body} =~ s/\r\n/\n/sg             if $mainSetting{dos2unixlinebreaks};
        $self->mlb_one_sentence_per_line_indent    if $is_m_switch_active;
        $self->_mlb_after_indentation_token_adjust if $is_m_switch_active;

        # ---------- headings -------------------
        $self->find_heading;

        $self->condense_blank_lines
            if ( $is_m_switch_active and ${ $mainSetting{modifyLineBreaks} }{condenseMultipleBlankLinesInto} );

        # ---------- one sentence per line, text wrap (AFTER) -------------------
        $self->mlb_POST_indent_sentence_and_text_wrap if $is_m_switch_active;

        $self->unprotect_blank_lines
            if ( $is_m_switch_active and ${ $mainSetting{modifyLineBreaks} }{preserveBlankLines} );
        $self->un_dodge_double_backslash;
        $self->max_indentation_check;

        $self->remove_trailing_whitespace( when => "after" );
        $self->make_replacements( when => "after" ) if $is_rv_switch_active;
        $self->put_verbatim_back_in( match => "everything-except-commands" );
        $self->put_trailing_comments_back_in;
        $self->put_verbatim_back_in( match => "just-commands" );
        $self->_mlb_file_starts_with_line_break( when => "after" ) if $is_m_switch_active;
        $self->make_replacements( when => "after" )                if ( $is_r_switch_active and !$is_rv_switch_active );
        ${$self}{body} =~ s/\r\n/\n/sg                             if $mainSetting{dos2unixlinebreaks};
        $self->check_if_different                                  if ${$self}{overwriteIfDifferent};
    }
    $self->output_indented_text;
    return;
}

sub output_indented_text {
    my $self = shift;

    $self->simple_diff() if $is_check_switch_active;

    $logger->info("*Output routine:");

    # if -overwrite is active then output to original fileName
    if ( ${$self}{overwrite} ) {

        # diacritics in file names (highlighted in https://github.com/cmhughes/latexindent.pl/pull/439)
        ${$self}{fileName} = ${$self}{fileName};

        $logger->info("Overwriting file ${$self}{fileName}");
        my $OUTPUTFILE = open_with_encode( '>:encoding(UTF-8)', ${$self}{fileName} );
        print $OUTPUTFILE ${$self}{body};
        close($OUTPUTFILE);
    }
    elsif ( $switch{outputToFile} ) {
        $logger->info("Outputting to file ${$self}{outputToFile}");
        my $OUTPUTFILE = open_with_encode( '>:encoding(UTF-8)', ${$self}{outputToFile} );
        print $OUTPUTFILE ${$self}{body};
        close($OUTPUTFILE);
    }
    else {
        $logger->info("Not outputting to file; see -w and -o switches for more options.");
    }

    # output to screen, unless silent mode
    print ${$self}{body} unless $switch{silentMode};

    return;
}

sub output_logfile {

    my $self = shift;
    #
    # put the final line in the logfile
    $logger->info("${$mainSetting{logFilePreferences}}{endLogFileWith}")
        if ${ $mainSetting{logFilePreferences} }{endLogFileWith};

    # github info line
    $logger->info("*Please direct all communication/issues to:")
        if ${ $mainSetting{logFilePreferences} }{showGitHubInfoFooter};
    $logger->info("https://github.com/cmhughes/latexindent.pl")
        if ${ $mainSetting{logFilePreferences} }{showGitHubInfoFooter};
    $logger->info("documentation: https://latexindentpl.readthedocs.io/en/latest/")
        if ${ $mainSetting{logFilePreferences} }{showGitHubInfoFooter};

    # open log file
    my $logfileName = $switch{logFileName} || "indent.log";

    my $logfilePath;
    $logfilePath = "${$self}{cruftDirectory}/$logfileName";
    $logfilePath =~ s/\\/\//g;
    $logfilePath =~ s/\/{2,}/\//g;
    if ( $^O eq 'MSWin32' ) {
        $logfilePath =~ s/\//\\/g;
    }

    my $logfile = open_with_encode( '>:encoding(UTF-8)', $logfilePath );

    if ($logfile) {
        foreach my $line ( @{LatexIndent::Logger::logFileLines} ) {
            print $logfile $line, "\n";
        }

        # close log file
        close($logfile);
    }
    else {
        if ( $switch{screenlog} ) {
            print "WARN:  Could not open the logfile $logfilePath \n";
            print "       No logfile will be produced.\n";
        }
    }
}

1;
