#!/usr/bin/env perl
require 5.003;
$indent=4;
use warnings;

#
# Copyright by The HDF Group.
# All rights reserved.
#
# This file is part of HDF5.  The full HDF5 copyright notice, including
# terms governing use, modification, and redistribution, is contained in
# the COPYING file, which can be found at the root of the source code
# distribution tree, or in https://www.hdfgroup.org/licenses.
# If you do not have access to either file, you may request a copy from
# help@hdfgroup.org.
#

# Create error headers
#
# Read in the error description text file and create the appropriate headers
# needed by the library.
#

##############################################################################
# Print the copyright into an open file
#
sub print_copyright ($) {
    my $fh = shift;

    print $fh "/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n";
    print $fh " * Copyright by The HDF Group.                                               *\n";
    print $fh " * All rights reserved.                                                      *\n";
    print $fh " *                                                                           *\n";
    print $fh " * This file is part of HDF5.  The full HDF5 copyright notice, including     *\n";
    print $fh " * terms governing use, modification, and redistribution, is contained in    *\n";
    print $fh " * the COPYING file, which can be found at the root of the source code       *\n";
    print $fh " * distribution tree, or in https://www.hdfgroup.org/licenses.               *\n";
    print $fh " * If you do not have access to either file, you may request a copy from     *\n";
    print $fh " * help\@hdfgroup.org.                                                        *\n";
    print $fh " * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */\n";
}

##############################################################################
# Print the "do not change this file" warning
#
sub print_warning ($) {
    my $fh = shift;

    print $fh "\n/* Generated automatically by bin/make_err -- do not edit */\n";
    print $fh "/* Add new errors to H5err.txt file */\n";
}

##############################################################################
# Print start of ifdef's to prevent a file from being re-included
#
sub print_startprotect ($$) {
    my ($fh, $file) = @_;

    # Clip off the ".h" part of the name
    $file =~ s/(\w*)\.h/$1/;

    # Print the ifdef info
    print $fh "\n#ifndef ${file}_H\n";
    print $fh "#define ${file}_H\n";
}

##############################################################################
# Print end of ifdef's to prevent a file from being re-included
#
sub print_endprotect ($$) {
    my ($fh, $file) = @_;

    # Clip off the ".h" part of the name
    $file =~ s/(\w*)\.h/$1/;

    # Print the endif info
    print $fh "\n#endif /* ${file}_H */\n";
}

##############################################################################
# Parse a meaningful line (not a comment or blank line) into the appropriate
# data structure
#
sub parse_line ($) {
    my $line = shift;   # Get the line to parse
    my $name;           # The name of the error message
    my $desc;           # The description of the error message

    # Parse major error lines
#print "line=$line\n";
    if($line =~ /^\s*MAJOR,/) {
        # Get the major error's name & description
        ($name, $desc) = ($line =~ /^\s*MAJOR,\s*(\w*),\s*(.*)\n/);
#print "MAJOR: name=$name, desc=$desc\n";

        # Check if the name already exists as a major or minor error message
        if(exists($major{$name}) || exists($minor{$name})) {
            die "duplicated name: $name";
        }

        # Store the major errors in a hash table, indexed by the name
        $major{$name}=$desc;
    }
    # Parse minor error lines
    elsif($line =~ /^\s*MINOR,/) {
        my $min_section;           # Minor errors have a section they belong to also

        # Get the minor error's section, name & description
        ($min_section, $name, $desc) = ($line =~ /^\s*MINOR,\s*(\w*),\s*(\w*),\s*(.*)\n/);
#print "MINOR: min_section=$min_section, name=$name, desc=$desc\n";

        # Check for valid section
        if(!exists($section{$min_section})) {
            die "unknown section: $min_section";
        }

        # Check if the name already exists as a major or minor error message
        if(exists($major{$name}) || exists($minor{$name})) {
            die "duplicated name: $name";
        }

        # Store the minor errors in a hash table, indexed by the name
        $minor{$name}=$desc;

        # Add the minor error to the list for the section
        push @{$section_list{$min_section}}, $name;
    }
    # Parse section lines
    elsif($line =~ /^\s*SECTION,/) {
        # Get the section's name & description
        ($name, $desc) = ($line =~ /^\s*SECTION,\s*(\w*),\s*(.*)\n/);
#print "SECTION: name=$name, desc=$desc\n";

        # Check if the section has already been defined
        if(exists($section{$name})) {
            die "duplicated name: $name";
        }

        # Store the section in a hash table, indexed by the name
        $section{$name}=$desc;
    }
    # Unknown keyword
    else {
      die "unknown keyword: $line";
    }
}

##############################################################################
# Create the generated portion of the public header file
#
sub create_public ($) {
    my $prefix = shift;         # Get the prefix for the generated file
    my $file = "H5Epubgen.h";   # Name of file to generate
    my $name;                   # Name of error message
    my $desc;                   # Description of error message
    my $sect_name;              # Section of minor error messages
    my $sect_desc;              # Description of section

    # Rename previous file
#    rename "${prefix}${file}", "${prefix}${file}~" or die "unable to make backup";

    # Open new header file
    open HEADER, ">${prefix}${file}" or die "unable to modify source";

    # Create file contents

    print_copyright(*HEADER);
    print_warning(*HEADER);
    print_startprotect(*HEADER, $file);

    # Begin extern C block
    print HEADER "\n";
    print HEADER "#ifdef __cplusplus\n";
    print HEADER "extern \"C\" {\n";
    print HEADER "#endif\n";

    # Iterate over all the major errors
    print HEADER "\n/*********************/\n";
    print HEADER   "/* Major error codes */\n";
    print HEADER   "/*********************/\n\n";
    foreach $name (sort keys %major) {
        printf HEADER "#define %-20s (H5OPEN %s_g)\n",$name,$name;
    }
    foreach $name (sort keys %major) {
        printf HEADER "H5_DLLVAR hid_t %-20s /* %s */\n","${name}_g;",$major{$name};
    }

    # Iterate over all the minor error sections
    print HEADER "\n/*********************/\n";
    print HEADER   "/* Minor error codes */\n";
    print HEADER   "/*********************/\n";
    foreach $sect_name (sort keys %section) {
        print HEADER "\n/* $section{$sect_name} */\n";

        # Iterate over all the minor errors in each section
        for $name (sort @{$section_list{$sect_name}}) {
            printf HEADER "#define %-20s (H5OPEN %s_g)\n",$name,$name;
        }
        for $name (sort @{$section_list{$sect_name}}) {
            printf HEADER "H5_DLLVAR hid_t %-20s /* %s */\n","${name}_g;",$minor{$name};
        }
    }

    # End extern C block
    print HEADER "\n";
    print HEADER "#ifdef __cplusplus\n";
    print HEADER "}\n";
    print HEADER "#endif\n";

    print_endprotect(*HEADER, $file);

    # Close header file
    close HEADER;
}

##############################################################################
# Create the generated portion of the H5E major message definition code
#
sub create_majdef ($) {
    my $prefix = shift;         # Get the prefix for the generated file
    my $file = "H5Emajdef.h";   # Name of file to generate
    my $name;                   # Name of error message

    # Rename previous file
#    rename "${prefix}${file}", "${prefix}${file}~" or die "unable to make backup";

    # Open new header file
    open HEADER, ">${prefix}${file}" or die "unable to modify source";

    # Create file contents

    print_copyright(*HEADER);
    print_warning(*HEADER);
    print_startprotect(*HEADER, $file);

    # Iterate over all the major errors
    print HEADER "\n/***********************************/\n";
    print HEADER   "/* Major error message definitions */\n";
    print HEADER   "/***********************************/\n\n";
    print HEADER   "/* clang-format off */\n";
    foreach $name (sort keys %major) {
        printf HEADER "static const H5E_msg_t ${name}_msg_s = {false, \"${major{$name}}\", H5E_MAJOR, &H5E_err_cls_s};\n";
    }
    print HEADER   "/* clang-format on */\n";

    print_endprotect(*HEADER, $file);

    # Close header file
    close HEADER;
}

##############################################################################
# Create the generated portion of the H5E minor message definition code
#
sub create_mindef ($) {
    my $prefix = shift;         # Get the prefix for the generated file
    my $file = "H5Emindef.h";     # Name of file to generate
    my $name;                   # Name of error message
    my $sect_name;              # Section of minor error messages

    # Rename previous file
#    rename "${prefix}${file}", "${prefix}${file}~" or die "unable to make backup";

    # Open new header file
    open HEADER, ">${prefix}${file}" or die "unable to modify source";

    # Create file contents

    print_copyright(*HEADER);
    print_warning(*HEADER);
    print_startprotect(*HEADER, $file);

    # Iterate over all the minor error sections
    print HEADER "\n/***********************************/\n";
    print HEADER   "/* Minor error message definitions */\n";
    print HEADER   "/***********************************/\n\n";
    print HEADER   "/* clang-format off */\n";
    foreach $sect_name (sort keys %section) {
        print HEADER "\n/* $sect_name: $section{$sect_name} */\n";

        # Iterate over all the minor errors in each section
        for $name (sort @{$section_list{$sect_name}}) {
            printf HEADER "static const H5E_msg_t ${name}_msg_s = {false, \"${minor{$name}}\", H5E_MINOR, &H5E_err_cls_s};\n";
        }
    }
    print HEADER   "/* clang-format on */\n";

    print_endprotect(*HEADER, $file);

    # Close header file
    close HEADER;
}

##############################################################################
# Create the generated portion of the H5E initialization code
#
sub create_init ($) {
    my $prefix = shift;         # Get the prefix for the generated file
    my $file = "H5Einit.h";     # Name of file to generate
    my $name;                   # Name of error message
    my $last_name;              # Name of previous error message
    my $desc;                   # Description of error message
    my $sect_name;              # Section of minor error messages
    my $sect_desc;              # Description of section
    my $first_major = 0;        # Whether the first major error code was saved
    my $first_minor = 0;        # Whether the first minor error code was saved

    # Rename previous file
#    rename "${prefix}${file}", "${prefix}${file}~" or die "unable to make backup";

    # Open new header file
    open HEADER, ">${prefix}${file}" or die "unable to modify source";

    # Create file contents

    print_copyright(*HEADER);
    print_warning(*HEADER);
    print_startprotect(*HEADER, $file);

    # Iterate over all the major errors
    print HEADER "\n/*********************/\n";
    print HEADER   "/* Major error codes */\n";
    print HEADER   "/*********************/\n\n";
    foreach $name (sort keys %major) {
        print HEADER "/* $name */\n";
        print HEADER " "x(0*$indent),"assert(H5I_INVALID_HID == ${name}_g);\n";
        print HEADER " "x(0*$indent),"if((${name}_g = H5I_register(H5I_ERROR_MSG, &${name}_msg_s, false)) < 0)\n";
        print HEADER " "x(1*$indent),"HGOTO_ERROR(H5E_ERROR, H5E_CANTREGISTER, FAIL, \"can't register error message\");\n";
        if ($first_major == 0) {
            print HEADER " "x(0*$indent),"\n/* Remember first major error code ID */\n";
            print HEADER " "x(0*$indent),"assert(H5E_first_maj_id_g==H5I_INVALID_HID);\n";
            print HEADER " "x(0*$indent),"H5E_first_maj_id_g = ${name}_g;\n\n";
            $first_major = 1;
        }
        $last_name = $name;
    }
    print HEADER " "x(0*$indent),"\n/* Remember last major error code ID */\n";
    print HEADER " "x(0*$indent),"assert(H5E_last_maj_id_g==H5I_INVALID_HID);\n";
    print HEADER " "x(0*$indent),"H5E_last_maj_id_g = ${last_name}_g;\n\n";

    # Iterate over all the minor error sections
    print HEADER "\n/*********************/\n";
    print HEADER   "/* Minor error codes */\n";
    print HEADER   "/*********************/\n\n";
    foreach $sect_name (sort keys %section) {
        print HEADER "\n/* $section{$sect_name} */\n";

        # Iterate over all the minor errors in each section
        for $name (sort @{$section_list{$sect_name}}) {
            print HEADER "/* $name */\n";
            print HEADER " "x(0*$indent),"assert(H5I_INVALID_HID == ${name}_g);\n";
            print HEADER " "x(0*$indent),"if((${name}_g = H5I_register(H5I_ERROR_MSG, &${name}_msg_s, false)) < 0)\n";
            print HEADER " "x(1*$indent),"HGOTO_ERROR(H5E_ERROR, H5E_CANTREGISTER, FAIL, \"can't register error message\");\n";

            if ($first_minor == 0) {
                print HEADER " "x(0*$indent),"\n/* Remember first minor error code ID */\n";
                print HEADER " "x(0*$indent),"assert(H5E_first_min_id_g==H5I_INVALID_HID);\n";
                print HEADER " "x(0*$indent),"H5E_first_min_id_g = ${name}_g;\n\n";
                $first_minor = 1;
            }
            $last_name = $name;
        }
    }
    print HEADER " "x(0*$indent),"\n/* Remember last minor error code ID */\n";
    print HEADER " "x(0*$indent),"assert(H5E_last_min_id_g==H5I_INVALID_HID);\n";
    print HEADER " "x(0*$indent),"H5E_last_min_id_g = ${last_name}_g;\n";

    print_endprotect(*HEADER, $file);

    # Close header file
    close HEADER;
}

##############################################################################
# Create the generated portion of the H5E termination code
#
sub create_term ($) {
    my $prefix = shift;         # Get the prefix for the generated file
    my $file = "H5Eterm.h";     # Name of file to generate
    my $name;                   # Name of error message
    my $desc;                   # Description of error message
    my $sect_name;              # Section of minor error messages
    my $sect_desc;              # Description of section

    # Rename previous file
#    rename "${prefix}${file}", "${prefix}${file}~" or die "unable to make backup";

    # Open new header file
    open HEADER, ">${prefix}${file}" or die "unable to modify source";

    # Create file contents

    print_copyright(*HEADER);
    print_warning(*HEADER);
    print_startprotect(*HEADER, $file);

    # Iterate over all the major errors
    print HEADER "\n/* Reset major error IDs */\n";
    foreach $name (sort keys %major) {
        print HEADER " "x($indent),"${name}_g =\n";
    }
    print HEADER " "x(2*$indent),"H5I_INVALID_HID;\n";
    print HEADER "\n"," "x(0*$indent),"H5E_first_maj_id_g = H5I_INVALID_HID;\n";
    print HEADER " "x(0*$indent),"H5E_last_maj_id_g = H5I_INVALID_HID;\n\n";

    # Iterate over all the minor error sections
    print HEADER "\n/* Reset minor error IDs */\n";
    foreach $sect_name (sort keys %section) {
        print HEADER "\n/* $sect_name: $section{$sect_name} */\n";

        # Iterate over all the minor errors in each section
        for $name (sort @{$section_list{$sect_name}}) {
            print HEADER " "x($indent),"${name}_g =\n";
        }
    }
    print HEADER " "x(2*$indent),"H5I_INVALID_HID;\n";
    print HEADER "\n"," "x(0*$indent),"H5E_first_min_id_g = H5I_INVALID_HID;\n";
    print HEADER " "x(0*$indent),"H5E_last_min_id_g = H5I_INVALID_HID;\n\n";

    print_endprotect(*HEADER, $file);

    # Close header file
    close HEADER;
}

##############################################################################
# Create the generated portion of the error code definitions
#
sub create_define ($) {
    my $prefix = shift;         # Get the prefix for the generated file
    my $file = "H5Edefin.h";    # Name of file to generate
    my $name;                   # Name of error message
    my $desc;                   # Description of error message
    my $sect_name;              # Section of minor error messages
    my $sect_desc;              # Description of section
    my $num_msg;                # Number of messages

    # Rename previous file
#    rename "${prefix}${file}", "${prefix}${file}~" or die "unable to make backup";

    # Open new header file
    open HEADER, ">${prefix}${file}" or die "unable to modify source";

    # Create file contents

    print_copyright(*HEADER);
    print_warning(*HEADER);
    print_startprotect(*HEADER, $file);

    # Iterate over all the major errors
    $num_msg = 0;
    print HEADER "\n/* Major error IDs */\n";
    foreach $name (sort keys %major) {
        printf HEADER "hid_t %-20s = H5I_INVALID_HID;      /* %s */\n","${name}_g",$major{$name};
        $num_msg++;
    }
    print HEADER "\n/* Number of major error messages */\n";
    printf HEADER "#define H5E_NUM_MAJ_ERRORS %d\n", $num_msg;

    # Iterate over all the minor error sections
    $num_msg = 0;
    print HEADER "\n/* Minor error IDs */\n";
    foreach $sect_name (sort keys %section) {
        print HEADER "\n/* $sect_name: $section{$sect_name} */\n";

        # Iterate over all the minor errors in each section
        for $name (sort @{$section_list{$sect_name}}) {
            printf HEADER "hid_t %-20s = H5I_INVALID_HID;      /* %s */\n","${name}_g",$minor{$name};
            $num_msg++;
        }
    }
    print HEADER "\n/* Number of minor error messages */\n";
    printf HEADER "#define H5E_NUM_MIN_ERRORS %d\n", $num_msg;

    print_endprotect(*HEADER, $file);

    # Close header file
    close HEADER;
}

##############################################################################
# Read error file (given as command-line argument) in and process it into
# internal data structures, then create error header files.
#
for $file (@ARGV) {
    my $prefix;         # Local prefix for generated files

    ($prefix) = ($file =~ /(^.*\/)/);
    # Read in the entire file
    open SOURCE, $file or die "$file: $!\n";
    while ( defined ($line=<SOURCE>) ) {
        if(!($line =~ /(^\s*#.*$)|(^\s*$)/)) {
            # Construct data structures for later printing
            parse_line($line);
        }
    }
    close SOURCE;

    # Create header files
    print "Generating 'H5Epubgen.h'\n";
    create_public($prefix);
    print "Generating 'H5Emajdef.h'\n";
    create_majdef($prefix);
    print "Generating 'H5Emindef.h'\n";
    create_mindef($prefix);
    print "Generating 'H5Einit.h'\n";
    create_init($prefix);
    print "Generating 'H5Eterm.h'\n";
    create_term($prefix);
    print "Generating 'H5Edefin.h'\n";
    create_define($prefix);
}
