/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define CHAZ_USE_SHORT_NAMES

#include "Charmonizer/Core/HeaderChecker.h"
#include "Charmonizer/Core/Compiler.h"
#include "Charmonizer/Core/ConfWriter.h"
#include "Charmonizer/Core/Util.h"
#include "Charmonizer/Probe/Integers.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

/* Determine endian-ness of this machine.
 */
static chaz_bool_t
S_machine_is_big_endian(void);

static const char sizes_code[] =
    QUOTE(  #include "_charm.h"                       )
    QUOTE(  int main () {                             )
    QUOTE(      Charm_Setup;                          )
    QUOTE(      printf("%d ", (int)sizeof(char));     )
    QUOTE(      printf("%d ", (int)sizeof(short));    )
    QUOTE(      printf("%d ", (int)sizeof(int));      )
    QUOTE(      printf("%d ", (int)sizeof(long));     )
    QUOTE(      printf("%d ", (int)sizeof(void*));    )
    QUOTE(      return 0;                             )
    QUOTE(  }                                         );

static const char type64_code[] =
    QUOTE(  #include "_charm.h"                       )
    QUOTE(  int main()                                )
    QUOTE(  {                                         )
    QUOTE(      Charm_Setup;                          )
    QUOTE(      printf("%%d", (int)sizeof(%s));       )
    QUOTE(      return 0;                             )
    QUOTE(  }                                         );

static const char literal64_code[] =
    QUOTE(  #include "_charm.h"                       )
    QUOTE(  #define big 9000000000000000000%s         )
    QUOTE(  int main()                                )
    QUOTE(  {                                         )
    QUOTE(      int truncated = (int)big;             )
    QUOTE(      Charm_Setup;                          )
    QUOTE(      printf("%%d\n", truncated);           )
    QUOTE(      return 0;                             )
    QUOTE(  }                                         );

void
Integers_run(void) {
    char *output;
    size_t output_len;
    int sizeof_char       = -1;
    int sizeof_short      = -1;
    int sizeof_int        = -1;
    int sizeof_ptr        = -1;
    int sizeof_long       = -1;
    int sizeof_long_long  = -1;
    int sizeof___int64    = -1;
    chaz_bool_t has_8     = false;
    chaz_bool_t has_16    = false;
    chaz_bool_t has_32    = false;
    chaz_bool_t has_64    = false;
    chaz_bool_t has_long_long = false;
    chaz_bool_t has___int64   = false;
    chaz_bool_t has_inttypes  = HeadCheck_check_header("inttypes.h");
    chaz_bool_t has_stdint    = HeadCheck_check_header("stdint.h");
    char i32_t_type[10];
    char i32_t_postfix[10];
    char u32_t_postfix[10];
    char i64_t_type[10];
    char i64_t_postfix[10];
    char u64_t_postfix[10];
    char code_buf[1000];

    ConfWriter_start_module("Integers");

    /* Document endian-ness. */
    if (S_machine_is_big_endian()) {
        ConfWriter_append_conf("#define CHY_BIG_END\n");
    }
    else {
        ConfWriter_append_conf("#define CHY_LITTLE_END\n");
    }

    /* Record sizeof() for several common integer types. */
    output = CC_capture_output(sizes_code, strlen(sizes_code), &output_len);
    if (output != NULL) {
        char *end_ptr = output;

        sizeof_char  = strtol(output, &end_ptr, 10);
        output       = end_ptr;
        sizeof_short = strtol(output, &end_ptr, 10);
        output       = end_ptr;
        sizeof_int   = strtol(output, &end_ptr, 10);
        output       = end_ptr;
        sizeof_long  = strtol(output, &end_ptr, 10);
        output       = end_ptr;
        sizeof_ptr   = strtol(output, &end_ptr, 10);
    }

    /* Determine whether long longs are available. */
    sprintf(code_buf, type64_code, "long long");
    output = CC_capture_output(code_buf, strlen(code_buf), &output_len);
    if (output != NULL) {
        has_long_long    = true;
        sizeof_long_long = strtol(output, NULL, 10);
    }

    /* Determine whether the __int64 type is available. */
    sprintf(code_buf, type64_code, "__int64");
    output = CC_capture_output(code_buf, strlen(code_buf), &output_len);
    if (output != NULL) {
        has___int64 = true;
        sizeof___int64 = strtol(output, NULL, 10);
    }

    /* Figure out which integer types are available. */
    if (sizeof_char == 1) {
        has_8 = true;
    }
    if (sizeof_short == 2) {
        has_16 = true;
    }
    if (sizeof_int == 4) {
        has_32 = true;
        strcpy(i32_t_type, "int");
        strcpy(i32_t_postfix, "");
        strcpy(u32_t_postfix, "U");
    }
    else if (sizeof_long == 4) {
        has_32 = true;
        strcpy(i32_t_type, "long");
        strcpy(i32_t_postfix, "L");
        strcpy(u32_t_postfix, "UL");
    }
    if (sizeof_long == 8) {
        has_64 = true;
        strcpy(i64_t_type, "long");
    }
    else if (sizeof_long_long == 8) {
        has_64 = true;
        strcpy(i64_t_type, "long long");
    }
    else if (sizeof___int64 == 8) {
        has_64 = true;
        strcpy(i64_t_type, "__int64");
    }

    /* Probe for 64-bit literal syntax. */
    if (has_64 && sizeof_long == 8) {
        strcpy(i64_t_postfix, "L");
        strcpy(u64_t_postfix, "UL");
    }
    else if (has_64) {
        sprintf(code_buf, literal64_code, "LL");
        output = CC_capture_output(code_buf, strlen(code_buf), &output_len);
        if (output != NULL) {
            strcpy(i64_t_postfix, "LL");
        }
        else {
            sprintf(code_buf, literal64_code, "i64");
            output = CC_capture_output(code_buf, strlen(code_buf),
                                       &output_len);
            if (output != NULL) {
                strcpy(i64_t_postfix, "i64");
            }
            else {
                Util_die("64-bit types, but no literal syntax found");
            }
        }
        sprintf(code_buf, literal64_code, "ULL");
        output = CC_capture_output(code_buf, strlen(code_buf), &output_len);
        if (output != NULL) {
            strcpy(u64_t_postfix, "ULL");
        }
        else {
            sprintf(code_buf, literal64_code, "Ui64");
            output = CC_capture_output(code_buf, strlen(code_buf), &output_len);
            if (output != NULL) {
                strcpy(u64_t_postfix, "Ui64");
            }
            else {
                Util_die("64-bit types, but no literal syntax found");
            }
        }
    }

    /* Write out some conditional defines. */
    if (has_inttypes) {
        ConfWriter_append_conf("#define CHY_HAS_INTTYPES_H\n");
    }
    if (has_stdint) {
        ConfWriter_append_conf("#define CHY_HAS_STDINT_H\n");
    }
    if (has_long_long) {
        ConfWriter_append_conf("#define CHY_HAS_LONG_LONG\n");
    }
    if (has___int64) {
        ConfWriter_append_conf("#define CHY_HAS___INT64\n");
    }

    /* Write out sizes. */
    ConfWriter_append_conf("#define CHY_SIZEOF_CHAR %d\n",  sizeof_char);
    ConfWriter_append_conf("#define CHY_SIZEOF_SHORT %d\n", sizeof_short);
    ConfWriter_append_conf("#define CHY_SIZEOF_INT %d\n",   sizeof_int);
    ConfWriter_append_conf("#define CHY_SIZEOF_LONG %d\n",  sizeof_long);
    ConfWriter_append_conf("#define CHY_SIZEOF_PTR %d\n",   sizeof_ptr);
    if (has_long_long) {
        ConfWriter_append_conf("#define CHY_SIZEOF_LONG_LONG %d\n",
                               sizeof_long_long);
    }
    if (has___int64) {
        ConfWriter_append_conf("#define CHY_SIZEOF___INT64 %d\n",
                               sizeof___int64);
    }

    /* Write affirmations, typedefs and maximums/minimums. */
    ConfWriter_append_conf("typedef int chy_bool_t;\n");
    if (has_stdint) {
        ConfWriter_append_conf("#include <stdint.h>\n");
    }
    else {
        /* we support only the following subset of stdint.h
         *   int8_t
         *   int16_t
         *   int32_t
         *   int64_t
         *   uint8_t
         *   uint16_t
         *   uint32_t
         *   uint64_t
         */
        if (has_8) {
            ConfWriter_append_conf(
                "typedef signed char int8_t;\n"
                "typedef unsigned char uint8_t;\n"
            );
        }
        if (has_16) {
            ConfWriter_append_conf(
                "typedef short int16_t;\n"
                "typedef unsigned short uint16_t;\n"
            );
        }
        if (has_32) {
            ConfWriter_append_conf(
                "typedef %s int32_t;\n", i32_t_type
            );
            ConfWriter_append_conf(
                "typedef unsigned %s uint32_t;\n", i32_t_type
            );
        }
        if (has_64) {
            ConfWriter_append_conf(
                "typedef %s int64_t;\n", i64_t_type
            );
            ConfWriter_append_conf(
                "typedef unsigned %s uint64_t;\n", i64_t_type
            );
        }
    }
    if (has_8) {
        ConfWriter_append_conf(
            "#define CHY_HAS_I8_T\n"
            "typedef signed char chy_i8_t;\n"
            "typedef unsigned char chy_u8_t;\n"
            "#define CHY_I8_MAX 0x7F\n"
            "#define CHY_I8_MIN (-I8_MAX - 1)\n"
            "#define CHY_U8_MAX (I8_MAX * 2 + 1)\n"
        );
    }
    if (has_16) {
        ConfWriter_append_conf(
            "#define CHY_HAS_I16_T\n"
            "typedef short chy_i16_t;\n"
            "typedef unsigned short chy_u16_t;\n"
            "#define CHY_I16_MAX 0x7FFF\n"
            "#define CHY_I16_MIN (-I16_MAX - 1)\n"
            "#define CHY_U16_MAX (I16_MAX * 2 + 1)\n"
        );
    }
    if (has_32) {
        ConfWriter_append_conf("#define CHY_HAS_I32_T\n");
        ConfWriter_append_conf("typedef %s chy_i32_t;\n", i32_t_type);
        ConfWriter_append_conf("typedef unsigned %s chy_u32_t;\n",
                               i32_t_type);
        ConfWriter_append_conf("#define CHY_I32_MAX 0x7FFFFFFF%s\n",
                               i32_t_postfix);
        ConfWriter_append_conf("#define CHY_I32_MIN (-I32_MAX - 1)\n");
        ConfWriter_append_conf("#define CHY_U32_MAX (I32_MAX * 2%s + 1%s)\n",
                               u32_t_postfix, u32_t_postfix);
    }
    if (has_64) {
        ConfWriter_append_conf("#define CHY_HAS_I64_T\n");
        ConfWriter_append_conf("typedef %s chy_i64_t;\n", i64_t_type);
        ConfWriter_append_conf("typedef unsigned %s chy_u64_t;\n",
                               i64_t_type);
        ConfWriter_append_conf("#define CHY_I64_MAX 0x7FFFFFFFFFFFFFFF%s\n",
                               i64_t_postfix);
        ConfWriter_append_conf("#define CHY_I64_MIN (-I64_MAX - 1%s)\n",
                               i64_t_postfix);
        ConfWriter_append_conf("#define CHY_U64_MAX (I64_MAX * 2%s + 1%s)\n",
                               u64_t_postfix, u64_t_postfix);
    }

    /* Create the I64P and U64P printf macros. */
    if (has_64) {
        int i;
        const char *options[] = {
            "ll",
            "l",
            "L",
            "q",  /* Some *BSDs */
            "I64", /* Microsoft */
            NULL,
        };

        /* Buffer to hold the code, and its start and end. */
        static const char format_64_code[] =
            QUOTE(  #include "_charm.h"                           )
            QUOTE(  int main() {                                  )
            QUOTE(      Charm_Setup;                              )
            QUOTE(      printf("%%%su", 18446744073709551615%s);  )
            QUOTE(      return 0;                                 )
            QUOTE( }                                              );

        for (i = 0; options[i] != NULL; i++) {
            /* Try to print 2**64-1, and see if we get it back intact. */
            sprintf(code_buf, format_64_code, options[i], u64_t_postfix);
            output = CC_capture_output(code_buf, strlen(code_buf),
                                       &output_len);

            if (output_len != 0
                && strcmp(output, "18446744073709551615") == 0
               ) {
                ConfWriter_append_conf("#define CHY_I64P \"%sd\"\n",
                                       options[i]);
                ConfWriter_append_conf("#define CHY_U64P \"%su\"\n",
                                       options[i]);
                break;
            }
        }

    }

    /* Write out the 32-bit and 64-bit literal macros. */
    if (has_32) {
        if (strcmp(i32_t_postfix, "") == 0) {
            ConfWriter_append_conf("#define CHY_I32_C(n) n\n");
            ConfWriter_append_conf("#define CHY_U32_C(n) n##%s\n",
                                   u32_t_postfix);
        }
        else {
            ConfWriter_append_conf("#define CHY_I32_C(n) n##%s\n",
                                   i32_t_postfix);
            ConfWriter_append_conf("#define CHY_U32_C(n) n##%s\n",
                                   u32_t_postfix);
        }
    }
    if (has_64) {
        ConfWriter_append_conf("#define CHY_I64_C(n) n##%s\n", i64_t_postfix);
        ConfWriter_append_conf("#define CHY_U64_C(n) n##%s\n", u64_t_postfix);
    }

    /* Create macro for promoting pointers to integers. */
    if (has_64) {
        if (sizeof_ptr == 8) {
            ConfWriter_append_conf("#define CHY_PTR_TO_I64(ptr) "
                                   "((chy_i64_t)(chy_u64_t)(ptr))\n");
        }
        else {
            ConfWriter_append_conf("#define CHY_PTR_TO_I64(ptr) "
                                   "((chy_i64_t)(chy_u32_t)(ptr))\n");
        }
    }

    /* True and false. */
    ConfWriter_append_conf(
        "#ifndef true\n"
        "  #define true 1\n"
        "#endif\n"
        "#ifndef false\n"
        "  #define false 0\n"
        "#endif\n"
    );

    /* Shorten. */
    ConfWriter_start_short_names();
    if (S_machine_is_big_endian()) {
        ConfWriter_shorten_macro("BIG_END");
    }
    else {
        ConfWriter_shorten_macro("LITTLE_END");
    }
    ConfWriter_shorten_macro("SIZEOF_CHAR");
    ConfWriter_shorten_macro("SIZEOF_SHORT");
    ConfWriter_shorten_macro("SIZEOF_LONG");
    ConfWriter_shorten_macro("SIZEOF_INT");
    ConfWriter_shorten_macro("SIZEOF_PTR");
    if (has_long_long) {
        ConfWriter_shorten_macro("HAS_LONG_LONG");
        ConfWriter_shorten_macro("SIZEOF_LONG_LONG");
    }
    if (has___int64) {
        ConfWriter_shorten_macro("HAS___INT64");
        ConfWriter_shorten_macro("SIZEOF___INT64");
    }
    if (has_inttypes) {
        ConfWriter_shorten_macro("HAS_INTTYPES_H");
    }
    ConfWriter_shorten_typedef("bool_t");
    if (has_8) {
        ConfWriter_shorten_macro("HAS_I8_T");
        ConfWriter_shorten_typedef("i8_t");
        ConfWriter_shorten_typedef("u8_t");
        ConfWriter_shorten_macro("I8_MAX");
        ConfWriter_shorten_macro("I8_MIN");
        ConfWriter_shorten_macro("U8_MAX");
    }
    if (has_16) {
        ConfWriter_shorten_macro("HAS_I16_T");
        ConfWriter_shorten_typedef("i16_t");
        ConfWriter_shorten_typedef("u16_t");
        ConfWriter_shorten_macro("I16_MAX");
        ConfWriter_shorten_macro("I16_MIN");
        ConfWriter_shorten_macro("U16_MAX");
    }
    if (has_32) {
        ConfWriter_shorten_macro("HAS_I32_T");
        ConfWriter_shorten_typedef("i32_t");
        ConfWriter_shorten_typedef("u32_t");
        ConfWriter_shorten_macro("I32_MAX");
        ConfWriter_shorten_macro("I32_MIN");
        ConfWriter_shorten_macro("U32_MAX");
        ConfWriter_shorten_macro("I32_C");
        ConfWriter_shorten_macro("U32_C");
    }
    if (has_64) {
        ConfWriter_shorten_macro("HAS_I64_T");
        ConfWriter_shorten_typedef("i64_t");
        ConfWriter_shorten_typedef("u64_t");
        ConfWriter_shorten_macro("I64_MAX");
        ConfWriter_shorten_macro("I64_MIN");
        ConfWriter_shorten_macro("U64_MAX");
        ConfWriter_shorten_macro("I64P");
        ConfWriter_shorten_macro("U64P");
        ConfWriter_shorten_macro("I64_C");
        ConfWriter_shorten_macro("U64_C");
        ConfWriter_shorten_macro("PTR_TO_I64");
    }
    ConfWriter_end_short_names();

    ConfWriter_end_module();
}

static chaz_bool_t
S_machine_is_big_endian(void) {
    long one = 1;
    return !(*((char*)(&one)));
}


