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

use Cyrus::IMAPOptions::App -command;

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

sub usage_desc { 'csource %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();
    $imapoptions->iterate(\&_print_option, $opt);
    $self->footer();
}

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

    my $c = <<~'END_HEADER';
    /* auto-generated by tools/imapoptions.pl */
    #include <config.h>
    #include <sys/types.h>
    #ifdef HAVE_UNISTD_H
    #include <unistd.h>
    #endif
    #include <stdlib.h>
    #include <string.h>
    #include "imapopts.h"

    /*
     * Sun C Compilers are more strict than GNU and won't allow type
     * casting to a union
     */

    #if defined(__SUNPRO_C) || defined(__SUNPRO_CC)
    #define U_CFG_V
    #else
    #define U_CFG_V (union config_value)
    #endif


    EXPORTED struct imapopt_s imapopts[] =
    {
      { IMAPOPT_ZERO, "", 0, OPT_NOTOPT, 0, NULL, IMAPOPT_ZERO, { NULL }, { NULL }, { { NULL, IMAP_ENUM_ZERO } } },
    END_HEADER

    print $c;
}

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

    my $c = <<~'END_FOOTER';
      { IMAPOPT_LAST, NULL, 0, OPT_NOTOPT, 0, NULL, IMAPOPT_ZERO, { NULL }, { NULL }, { { NULL, IMAP_ENUM_ZERO } } }
    };

    /* c code goes here */

    END_FOOTER

    print $c;
}

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

    return if $option->for_documentation_only;

    print '  { ';
    print join(', ',
               $option->c_name,
               '"' . $option->name . '"',
               '0',
               'OPT_' . $option->type,
          ), ",\n";
    print '    ';
    print join(', ',
               $option->c_last_modified,
               $option->c_deprecated_since,
               $option->c_replaced_by,
          ), ",\n";

    # We write the default value twice:
    # The first initialises the 'val' field of the struct, which is what makes
    # this the default value.  The second sets the 'def' field of the struct,
    # which libconfig uses to see when val has been changed from the default.
    my ($ctype, $default_value) = $option->c_default_value;
    for (1..2) {
        if ($opt->cc eq 'gcc') {
            print "    U_CFG_V(($ctype) $default_value),\n";
        }
        else {
            print "    {(void*)($default_value)},\n";
        }
    }

    print "    {";
    foreach my $pair ($option->c_allowed_values) {
        my ($v, $e) = @$pair;
        print " { \"$v\", $e },\n     ";
    }
    print " { NULL, IMAP_ENUM_ZERO } } },\n";
}

1;
