# SPDX-License-Identifier: BSD-3-Clause-CMU
# See COPYING file at the root of the distribution for more details.
package Cyrus::IMAPOptions::App::Command::cheader;
use warnings;
use strict;

use Cyrus::IMAPOptions::App -command;

use List::Util qw(max);

sub abstract { 'build c header file from imapoptions' }

sub usage_desc { 'cheader %o imapoptions' }

sub opt_spec { return Cyrus::IMAPOptions::App::common_opt_spec; }

sub validate_args
{
    my ($self, $opt, $args) = @_;

    $self->usage_error("need imapoptions path") if @$args < 1;
    $self->usage_error("too many arguments") if @$args > 1;

    $self->usage_error("imapoptions must be a directory")
        if ! -d $args->[0];
}

sub execute
{
    my ($self, $opt, $args) = @_;

    my $imapoptions = Cyrus::IMAPOptions->new(
        from_path => $args->[0],
        forbid_unreleased => $opt->forbid_unreleased,
    );

    $imapoptions->check_unreleased($self->app->arg0);

    $self->header();

    print "enum imapopt {\n";
    print "    IMAPOPT_ZERO = 0,\n";
    $imapoptions->iterate(\&_print_option);
    print "    IMAPOPT_LAST\n";
    print "};\n\n";

    print "enum enum_value {\n";
    print "    IMAP_ENUM_ZERO = 0,\n";
    $imapoptions->iterate(sub { $self->_print_enum_defs(@_) });
    print "};\n";

    my $max_enum_opts = 0;
    $imapoptions->iterate(sub {
        my (undef, $option) = @_;
        if ($option->has_allowed_values) {
            $max_enum_opts = max($max_enum_opts,
                                 $option->allowed_values->count);
        }
    });

    $self->footer($max_enum_opts);
}

sub header
{
    my ($self) = @_;

    my $c = <<~'END_HEADER';
    /* auto-generated by tools/imapoptions.pl */
    #ifndef INCLUDED_IMAPOPTS_H
    #define INCLUDED_IMAPOPTS_H

    #include <stdbool.h>
    #include <stdint.h>

    enum opttype {
        OPT_BYTESIZE,
        OPT_DURATION,
        OPT_BITFIELD,
        OPT_STRINGLIST,
        OPT_ENUM,
        OPT_SWITCH,
        OPT_INT,
        OPT_STRING,
        OPT_NOTOPT
    };

    END_HEADER

    print $c;
}

sub footer
{
    my ($self, $max_enum_opts) = @_;

    my $c = <<~"END_FOOTER";

    union config_value {
        const char *s;      /* OPT_STRING, OPT_STRINGLIST */
        int32_t i32;        /* OPT_INT, OPT_DURATION */
        bool b;             /* OPT_SWITCH */
        enum enum_value e;  /* OPT_ENUM */
        int64_t i64;        /* OPT_BYTESIZE */
        uint64_t u64;       /* OPT_BITFIELD */
    };

    struct enum_option_s {
        const char *name;
        const enum enum_value val;
    };

    #define MAX_ENUM_OPTS $max_enum_opts
    struct imapopt_s {
        const enum imapopt opt;
        const char *name;
        int seen;
        const enum opttype type;
        uint32_t last_modified;
        const char *deprecated_since;
        const enum imapopt replaced_by;
        union config_value val;
        const union config_value def;
        const int defunit;
        const struct enum_option_s enum_options[MAX_ENUM_OPTS+1];
    };

    extern struct imapopt_s imapopts[];

    #endif /* INCLUDED_IMAPOPTIONS_H */
    END_FOOTER

    print $c;
}

sub _print_option
{
    my ($name, $option) = @_;

    return if $option->for_documentation_only;

    print '    ' . $option->c_name . ",\n";
}

sub _print_enum_defs
{
    my ($self, $name, $option) = @_;

    return if $option->for_documentation_only;

    foreach my $pair ($option->c_enum_defs) {
        my ($name, $init) = @$pair;

        print '    ' . $name;
        print ' = ' . $init if defined $init;
        print ",\n";
    }
}

1;
