####################################################################################################################################
# Auto-Generate Option Definition for Parsing with getopt_long().
####################################################################################################################################
package pgBackRestBuild::Config::BuildParse;

use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';

use Cwd qw(abs_path);
use Exporter qw(import);
    our @EXPORT = qw();
use File::Basename qw(dirname);
use Storable qw(dclone);

use pgBackRestDoc::Common::Log;
use pgBackRestDoc::Common::String;
use pgBackRestDoc::ProjectInfo;

use pgBackRestBuild::Build::Common;
use pgBackRestBuild::Config::Build;
use pgBackRestBuild::Config::Data;

####################################################################################################################################
# Constants
####################################################################################################################################
use constant BLDLCL_FILE_DEFINE                                     => 'parse';

use constant BLDLCL_ENUM_OPTION_TYPE                                => '01-enumOptionType';

use constant BLDLCL_DATA_COMMAND                                    => '01-dataCommand';
use constant BLDLCL_DATA_OPTION_GROUP                               => '01-dataOptionGroup';
use constant BLDLCL_DATA_OPTION                                     => '02-dataOption';
use constant BLDLCL_DATA_OPTION_GETOPT                              => '03-dataOptionGetOpt';
use constant BLDLCL_DATA_OPTION_RESOLVE                             => '04-dataOptionResolve';

####################################################################################################################################
# Definitions for constants and data to build
####################################################################################################################################
my $strSummary = 'Config Parse Rules';

my $rhBuild =
{
    &BLD_FILE =>
    {
        &BLDLCL_FILE_DEFINE =>
        {
            &BLD_SUMMARY => $strSummary,

            &BLD_ENUM =>
            {
                &BLDLCL_ENUM_OPTION_TYPE =>
                {
                    &BLD_SUMMARY => 'Option type',
                    &BLD_NAME => 'ConfigOptionType',
                },
            },

            &BLD_DATA =>
            {
                &BLDLCL_DATA_COMMAND =>
                {
                    &BLD_SUMMARY => 'Command parse data',
                },

                &BLDLCL_DATA_OPTION_GROUP =>
                {
                    &BLD_SUMMARY => 'Option group parse data',
                },

                &BLDLCL_DATA_OPTION =>
                {
                    &BLD_SUMMARY => 'Option parse data',
                },

                &BLDLCL_DATA_OPTION_GETOPT =>
                {
                    &BLD_SUMMARY => 'Option data for getopt_long()',
                },

                &BLDLCL_DATA_OPTION_RESOLVE =>
                {
                    &BLD_SUMMARY => 'Order for option parse resolution',
                },
            },
        },
    },
};

####################################################################################################################################
# Generate enum names
####################################################################################################################################
sub buildConfigDefineOptionTypeEnum
{
    return bldEnum('cfgOptType', shift);
}

push @EXPORT, qw(buildConfigDefineOptionTypeEnum);

sub buildConfigCommandRoleEnum
{
    return bldEnum('cfgCmdRole', shift);
}

####################################################################################################################################
# Helper functions for building optional option data
####################################################################################################################################
sub renderAllowList
{
    my $ryAllowList = shift;
    my $bCommandIndent = shift;

    my $strIndent = $bCommandIndent ? '    ' : '';

    return
        "${strIndent}            PARSE_RULE_OPTION_OPTIONAL_ALLOW_LIST\n" .
        "${strIndent}            (\n" .
        "${strIndent}                " . join(",\n${strIndent}                ", bldQuoteList($ryAllowList)) .
        "\n" .
        "${strIndent}            ),\n";
}

sub renderDepend
{
    my $rhDepend = shift;
    my $bCommandIndent = shift;

    my $strIndent = $bCommandIndent ? '    ' : '';

    my $strDependOption = $rhDepend->{&CFGDEF_DEPEND_OPTION};
    my $ryDependList = $rhDepend->{&CFGDEF_DEPEND_LIST};

    if (defined($ryDependList))
    {
        my @stryQuoteList;

        foreach my $strItem (@{$ryDependList})
        {
            push(@stryQuoteList, "\"${strItem}\"");
        }

        return
            "${strIndent}            PARSE_RULE_OPTION_OPTIONAL_DEPEND_LIST\n" .
            "${strIndent}            (\n" .
            "${strIndent}                " . buildConfigOptionEnum($strDependOption) . ",\n" .
            "${strIndent}                " . join(",\n${strIndent}                ", bldQuoteList($ryDependList)) .
            "\n" .
            "${strIndent}            ),\n";
    }

    return
        "${strIndent}            PARSE_RULE_OPTION_OPTIONAL_DEPEND(" . buildConfigOptionEnum($strDependOption) . "),\n";
}

sub renderOptional
{
    my $rhOptional = shift;
    my $bCommand = shift;
    my $strCommand = shift;
    my $strOption = shift;

    my $strIndent = $bCommand ? '    ' : '';
    my $strBuildSourceOptional;
    my $bSingleLine = false;

    if (defined($rhOptional->{&CFGDEF_ALLOW_LIST}))
    {
        $strBuildSourceOptional .=
            (defined($strBuildSourceOptional) && !$bSingleLine ? "\n" : '') .
            renderAllowList($rhOptional->{&CFGDEF_ALLOW_LIST}, $bCommand);

        $bSingleLine = false;
    }

    if (defined($rhOptional->{&CFGDEF_ALLOW_RANGE}))
    {
        my @fyRange = @{$rhOptional->{&CFGDEF_ALLOW_RANGE}};

        my $iMultiplier = $rhOptional->{&CFGDEF_TYPE} eq CFGDEF_TYPE_TIME ? 1000 : 1;

        $strBuildSourceOptional =
            (defined($strBuildSourceOptional) && !$bSingleLine ? "\n" : '') .
            "${strIndent}            PARSE_RULE_OPTION_OPTIONAL_ALLOW_RANGE(" . ($fyRange[0] * $iMultiplier) . ', ' .
            ($fyRange[1] * $iMultiplier) . "),\n";

        $bSingleLine = true;
    }

    if (defined($rhOptional->{&CFGDEF_DEPEND}))
    {
        $strBuildSourceOptional .=
            (defined($strBuildSourceOptional) && !$bSingleLine ? "\n" : '') .
            renderDepend($rhOptional->{&CFGDEF_DEPEND}, $bCommand);

        $bSingleLine = defined($rhOptional->{&CFGDEF_DEPEND}{&CFGDEF_DEPEND_LIST}) ? false : true;
    }

    if (defined($rhOptional->{&CFGDEF_DEFAULT}))
    {
        $strBuildSourceOptional .=
            (defined($strBuildSourceOptional) && !$bSingleLine ? "\n" : '') .
            "${strIndent}            PARSE_RULE_OPTION_OPTIONAL_DEFAULT(" .
            ($rhOptional->{&CFGDEF_DEFAULT_LITERAL} ? '' : '"') .
            (defined($rhOptional->{&CFGDEF_TYPE}) && $rhOptional->{&CFGDEF_TYPE} eq CFGDEF_TYPE_TIME ?
                $rhOptional->{&CFGDEF_DEFAULT} * 1000 : $rhOptional->{&CFGDEF_DEFAULT}) .
            ($rhOptional->{&CFGDEF_DEFAULT_LITERAL} ? '' : '"') .
            "),\n";

        $bSingleLine = true;
    }

    if ($bCommand && defined($rhOptional->{&CFGDEF_REQUIRED}))
    {
        $strBuildSourceOptional .=
            (defined($strBuildSourceOptional) && !$bSingleLine ? "\n" : '') .
            "${strIndent}            PARSE_RULE_OPTION_OPTIONAL_REQUIRED(" .
                ($rhOptional->{&CFGDEF_REQUIRED} ? 'true' : 'false') . "),\n";

        $bSingleLine = true;
    }

    return $strBuildSourceOptional;
}

####################################################################################################################################
# Build configuration constants and data
####################################################################################################################################
sub buildConfigParse
{
    # Build option type enum
    # ------------------------------------------------------------------------------------------------------------------------------
    my $rhEnum = $rhBuild->{&BLD_FILE}{&BLDLCL_FILE_DEFINE}{&BLD_ENUM}{&BLDLCL_ENUM_OPTION_TYPE};

    foreach my $strOptionType (cfgDefineOptionTypeList())
    {
        # Build C enum
        my $strOptionTypeEnum = buildConfigDefineOptionTypeEnum($strOptionType);
        push(@{$rhEnum->{&BLD_LIST}}, $strOptionTypeEnum);
    };

    # Build command parse data
    # ------------------------------------------------------------------------------------------------------------------------------
    my $rhCommandDefine = cfgDefineCommand();

    my $strBuildSource =
        "static const ParseRuleCommand parseRuleCommand[CFG_COMMAND_TOTAL] =\n" .
        "{";

    foreach my $strCommand (sort(keys(%{$rhCommandDefine})))
    {
        my $rhCommand = $rhCommandDefine->{$strCommand};

        # Build command data
        $strBuildSource .=
            "\n" .
            "    //" . (qw{-} x 126) . "\n" .
            "    PARSE_RULE_COMMAND\n" .
            "    (\n" .
            "        PARSE_RULE_COMMAND_NAME(\"${strCommand}\"),\n";

        if ($rhCommand->{&CFGDEF_PARAMETER_ALLOWED})
        {
            $strBuildSource .=
                "        PARSE_RULE_COMMAND_PARAMETER_ALLOWED(true),\n";
        }

        $strBuildSource .=
            "\n" .
            "        PARSE_RULE_COMMAND_ROLE_VALID_LIST\n" .
            "        (\n";

        foreach my $strCommandRole (sort(keys(%{$rhCommand->{&CFGDEF_COMMAND_ROLE}})))
        {
            $strBuildSource .=
                "            PARSE_RULE_COMMAND_ROLE(" . buildConfigCommandRoleEnum($strCommandRole) . ")\n";
        }

        $strBuildSource .=
            "        ),\n";

        $strBuildSource .=
            "    ),\n";
    };

    $strBuildSource .=
        "};\n";

    $rhBuild->{&BLD_FILE}{&BLDLCL_FILE_DEFINE}{&BLD_DATA}{&BLDLCL_DATA_COMMAND}{&BLD_SOURCE} = $strBuildSource;

    # Build option group parse data
    # ------------------------------------------------------------------------------------------------------------------------------
    my $rhOptionGroupDefine = cfgDefineOptionGroup();

    $strBuildSource =
        "static const ParseRuleOptionGroup parseRuleOptionGroup[CFG_OPTION_GROUP_TOTAL] =\n" .
        "{";

    foreach my $strGroup (sort(keys(%{$rhOptionGroupDefine})))
    {
        $strBuildSource .=
            "\n" .
            "    //" . (qw{-} x 126) . "\n" .
            "    PARSE_RULE_OPTION_GROUP\n" .
            "    (\n" .
            "        PARSE_RULE_OPTION_GROUP_NAME(\"" . $strGroup . "\"),\n" .
            "    ),\n";
    }

    $strBuildSource .=
        "};\n";

    $rhBuild->{&BLD_FILE}{&BLDLCL_FILE_DEFINE}{&BLD_DATA}{&BLDLCL_DATA_OPTION_GROUP}{&BLD_SOURCE} = $strBuildSource;

    # Build option parse data
    # ------------------------------------------------------------------------------------------------------------------------------
    my $rhConfigDefine = cfgDefine();

    $strBuildSource =
        "static const ParseRuleOption parseRuleOption[CFG_OPTION_TOTAL] =\n" .
        "{";

    foreach my $strOption (sort(keys(%{$rhConfigDefine})))
    {
        my $rhOption = $rhConfigDefine->{$strOption};

        $strBuildSource .=
            "\n" .
            "    // " . (qw{-} x 125) . "\n" .
            "    PARSE_RULE_OPTION\n" .
            "    (\n" .
            "        PARSE_RULE_OPTION_NAME(\"${strOption}\"),\n" .
            "        PARSE_RULE_OPTION_TYPE(" . buildConfigDefineOptionTypeEnum($rhOption->{&CFGDEF_TYPE}) . "),\n" .
            "        PARSE_RULE_OPTION_REQUIRED(" . ($rhOption->{&CFGDEF_REQUIRED} ? 'true' : 'false') . "),\n" .
            "        PARSE_RULE_OPTION_SECTION(cfgSection" .
                (defined($rhOption->{&CFGDEF_SECTION}) ? ucfirst($rhOption->{&CFGDEF_SECTION}) : 'CommandLine') .
                "),\n";

        if ($rhOption->{&CFGDEF_SECURE})
        {
            $strBuildSource .=
                "        PARSE_RULE_OPTION_SECURE(true),\n";
        }

        if ($rhOption->{&CFGDEF_TYPE} eq CFGDEF_TYPE_HASH || $rhOption->{&CFGDEF_TYPE} eq CFGDEF_TYPE_LIST)
        {
            $strBuildSource .=
                "        PARSE_RULE_OPTION_MULTI(true),\n";
        }

        # Build group info
        # --------------------------------------------------------------------------------------------------------------------------
        if ($rhOption->{&CFGDEF_GROUP})
        {
            $strBuildSource .=
                "        PARSE_RULE_OPTION_GROUP_MEMBER(true),\n" .
                "        PARSE_RULE_OPTION_GROUP_ID(" . buildConfigOptionGroupEnum($rhOption->{&CFGDEF_GROUP}) . "),\n";
        }

        # Build command role valid lists
        #---------------------------------------------------------------------------------------------------------------------------
        my $strBuildSourceSub = "";

        foreach my $strCommandRole (CFGCMD_ROLE_DEFAULT, CFGCMD_ROLE_ASYNC, CFGCMD_ROLE_LOCAL, CFGCMD_ROLE_REMOTE)
        {
            $strBuildSourceSub = "";

            foreach my $strCommand (cfgDefineCommandList())
            {
                if (defined($rhOption->{&CFGDEF_COMMAND}{$strCommand}))
                {
                    if (defined($rhOption->{&CFGDEF_COMMAND}{$strCommand}{&CFGDEF_COMMAND_ROLE}{$strCommandRole}))
                    {
                        $strBuildSourceSub .=
                            "            PARSE_RULE_OPTION_COMMAND(" . buildConfigCommandEnum($strCommand) . ")\n";
                    }
                }
            }

            if ($strBuildSourceSub ne "")
            {
                $strBuildSource .=
                    "\n" .
                    "        PARSE_RULE_OPTION_COMMAND_ROLE_" . uc($strCommandRole) . "_VALID_LIST\n" .
                    "        (\n" .
                    $strBuildSourceSub .
                    "        ),\n";
            }
        }

        # Render optional data and command overrides
        # --------------------------------------------------------------------------------------------------------------------------
        my $strBuildSourceOptional = renderOptional($rhOption, false);

        foreach my $strCommand (cfgDefineCommandList())
        {
            my $strBuildSourceOptionalCommand;
            my $rhCommand = $rhOption->{&CFGDEF_COMMAND}{$strCommand};

            if (defined($rhCommand))
            {
                $strBuildSourceOptionalCommand = renderOptional($rhCommand, true, $strCommand, $strOption);

                if (defined($strBuildSourceOptionalCommand))
                {
                    $strBuildSourceOptional .=
                        (defined($strBuildSourceOptional) ? "\n" : '') .
                        "            PARSE_RULE_OPTION_OPTIONAL_COMMAND_OVERRIDE\n" .
                        "            (\n" .
                        "                PARSE_RULE_OPTION_OPTIONAL_COMMAND(" . buildConfigCommandEnum($strCommand) . "),\n" .
                        "\n" .
                        $strBuildSourceOptionalCommand .
                        "            )\n";
                }
            }
        }

        if (defined($strBuildSourceOptional))
        {
            $strBuildSource .=
                "\n" .
                "        PARSE_RULE_OPTION_OPTIONAL_LIST\n" .
                "        (\n" .
                $strBuildSourceOptional .
                "        ),\n";
        }

        $strBuildSource .=
            "    ),\n";
    }

    $strBuildSource .=
        "};\n";

    $rhBuild->{&BLD_FILE}{&BLDLCL_FILE_DEFINE}{&BLD_DATA}{&BLDLCL_DATA_OPTION}{&BLD_SOURCE} = $strBuildSource;

    # Build option list for getopt_long()
    #-------------------------------------------------------------------------------------------------------------------------------
    $strBuildSource =
        "static const struct option optionList[] =\n" .
        "{";

    foreach my $strOption (sort(keys(%{$rhConfigDefine})))
    {
        my $rhOption = $rhConfigDefine->{$strOption};
        my $strOptionEnum = buildConfigOptionEnum($strOption);
        my $strOptionArg = ($rhOption->{&CFGDEF_TYPE} ne CFGDEF_TYPE_BOOLEAN ? "        .has_arg = required_argument,\n" : '');
        my $strOptionPrefix = $rhConfigDefine->{$strOption}{&CFGDEF_PREFIX};

        my @stryOptionName = ($strOption);

        if (defined($rhOption->{&CFGDEF_DEPRECATE}))
        {
            foreach my $strOptionNameAlt (sort(keys(%{$rhOption->{&CFGDEF_DEPRECATE}})))
            {
                push(@stryOptionName, $strOptionNameAlt);
            }
        }

        $strBuildSource .=
            "\n" .
            "    // ${strOption} option" . (@stryOptionName > 1 ? ' and deprecations' : '') . "\n" .
            "    // " . (qw{-} x 125) . "\n";

        for (my $iOptionIdx = 1; $iOptionIdx <= $rhOption->{&CFGDEF_INDEX_TOTAL}; $iOptionIdx++)
        {
            for (my $iOptionNameIdx = 0; $iOptionNameIdx < @stryOptionName; $iOptionNameIdx++)
            {
                my $strOptionName = $stryOptionName[$iOptionNameIdx];
                my $rhNameAlt = $rhOption->{&CFGDEF_DEPRECATE}{$strOptionName};

                # Skip alt name if it is not valid for this option index
                if ($iOptionNameIdx > 0 && defined($rhNameAlt->{&CFGDEF_INDEX}) && $rhNameAlt->{&CFGDEF_INDEX} != $iOptionIdx)
                {
                    next;
                }

                # Generate output name
                my $strOptionNameOut = $strOptionName;
                my $strOptionConst;

                if (defined($strOptionPrefix))
                {
                    if ($iOptionNameIdx == 0)
                    {
                        $strOptionNameOut =
                            "${strOptionPrefix}${iOptionIdx}-" . substr($strOptionName, length($strOptionPrefix) + 1);
                    }
                    else
                    {
                        $strOptionNameOut =~ s/\?/$iOptionIdx/g;
                    }
                }

                # Generate option value used for parsing (offset is added so options don't conflict with getopt_long return values)
                my $strOptionFlag = 'PARSE_OPTION_FLAG |';

                # Build option constant name if this is the current name for the option
                if ($iOptionNameIdx == 0)
                {
                    if (!$rhConfigDefine->{$strOption}{&CFGDEF_GROUP})
                    {
                        $strOptionConst = "CFGOPT_" . uc($strOptionNameOut);
                        $strOptionConst =~ s/\-/_/g;
                    }
                }
                # Else use bare string and mark as deprecated
                else
                {
                    $strOptionFlag .= ' PARSE_DEPRECATE_FLAG |';
                }

                my $strOptionVal =
                    ($rhOption->{&CFGDEF_GROUP} ? "(" . ($iOptionIdx - 1) . " << PARSE_KEY_IDX_SHIFT) | " : '') . $strOptionEnum;

                # Add option
                $strBuildSource .=
                    "    {\n" .
                    "        .name = \"${strOptionNameOut}\",\n" .
                    $strOptionArg .
                    "        .val = ${strOptionFlag} ${strOptionVal},\n" .
                    "    },\n";

                # Add negation when defined
                if ($rhOption->{&CFGDEF_NEGATE} &&
                    !($iOptionNameIdx > 0 && defined($rhNameAlt->{&CFGDEF_NEGATE}) && !$rhNameAlt->{&CFGDEF_NEGATE}))
                {
                    $strBuildSource .=
                        "    {\n" .
                        "        .name = \"no-${strOptionNameOut}\",\n" .
                        "        .val = ${strOptionFlag} PARSE_NEGATE_FLAG | ${strOptionVal},\n" .
                        "    },\n";
                }

                # Add reset when defined
                if ($rhOption->{&CFGDEF_RESET} &&
                    !($iOptionNameIdx > 0 && defined($rhNameAlt->{&CFGDEF_RESET}) && !$rhNameAlt->{&CFGDEF_RESET}))
                {
                    $strBuildSource .=
                        "    {\n" .
                        "        .name = \"reset-${strOptionNameOut}\",\n" .
                        "        .val = ${strOptionFlag} PARSE_RESET_FLAG | ${strOptionVal},\n" .
                        "    },\n";
                }
            }
        }
    }

    # The option list needs to be terminated or getopt_long will just keep on reading
    $strBuildSource .=
        "    // Terminate option list\n" .
        "    {\n" .
        "        .name = NULL\n" .
        "    }\n" .
        "};\n";

    $rhBuild->{&BLD_FILE}{&BLDLCL_FILE_DEFINE}{&BLD_DATA}{&BLDLCL_DATA_OPTION_GETOPT}{&BLD_SOURCE} = $strBuildSource;

    # Build option resolve order list.  This allows the option validation in C to take place in a single pass.
    #
    # Always process the stanza option first since confusing error message are produced if it is missing.
    #-------------------------------------------------------------------------------------------------------------------------------
    my @stryResolveOrder = (buildConfigOptionEnum(CFGOPT_STANZA));
    my $rhResolved = {&CFGOPT_STANZA => true};
    my $bAllResolved;

    do
    {
        # Assume that we will finish on this loop
        $bAllResolved = true;

        # Loop through all options
        foreach my $strOption (sort(keys(%{$rhConfigDefine})))
        {
            my $bSkip = false;

            # Check the default depend
            my $strOptionDepend =
                ref($rhConfigDefine->{$strOption}{&CFGDEF_DEPEND}) eq 'HASH' ?
                    $rhConfigDefine->{$strOption}{&CFGDEF_DEPEND}{&CFGDEF_DEPEND_OPTION} :
                    $rhConfigDefine->{$strOption}{&CFGDEF_DEPEND};

            if (defined($strOptionDepend) && !$rhResolved->{$strOptionDepend})
            {
                # &log(WARN, "$strOptionDepend is not resolved");
                $bSkip = true;
            }

            # Check the command depends
            foreach my $strCommand (sort(keys(%{$rhConfigDefine->{$strOption}{&CFGDEF_COMMAND}})))
            {
                my $strOptionDepend =
                    ref($rhConfigDefine->{$strOption}{&CFGDEF_COMMAND}{$strCommand}{&CFGDEF_DEPEND}) eq 'HASH' ?
                        $rhConfigDefine->{$strOption}{&CFGDEF_COMMAND}{$strCommand}{&CFGDEF_DEPEND}{&CFGDEF_DEPEND_OPTION} :
                        $rhConfigDefine->{$strOption}{&CFGDEF_COMMAND}{$strCommand}{&CFGDEF_DEPEND};

                if (defined($strOptionDepend) && !$rhResolved->{$strOptionDepend})
                {
                    $bSkip = true;
                }
            }

            # If dependency was not found try again on the next loop
            if ($bSkip)
            {
                $bAllResolved = false;
            }
            # Else add option to resolve order list
            elsif (!$rhResolved->{$strOption})
            {
                push(@stryResolveOrder, buildConfigOptionEnum($strOption));

                $rhResolved->{$strOption} = true;
            }
        }
    }
    while (!$bAllResolved);

    $rhBuild->{&BLD_FILE}{&BLDLCL_FILE_DEFINE}{&BLD_DATA}{&BLDLCL_DATA_OPTION_RESOLVE}{&BLD_SOURCE} =
        "static const ConfigOption optionResolveOrder[] =\n" .
        "{\n" .
        "    " . join(",\n    ", @stryResolveOrder) . ",\n" .
        "};\n";

    return $rhBuild;
}

push @EXPORT, qw(buildConfigParse);

1;
