#!/usr/bin/env python3

"""
Autogenerated wrapper generator for OSS Valkey.

Parses '__wrap_' C function signatures from 'wrappers.h' and generates
'generated_wrappers.hpp' and 'generated_wrappers.cpp', providing
classes (RealValkey and MockValkey) for gtest-based testing.
The generated files wrap the real functions and provide mockable
interfaces using GoogleTest.
"""
import sys
import os

from wrapper_util import find_wrapper_functions_in_header


def generate_header(header_file, methods):
    # Write the generated_wrappers hpp file
    with open(header_file, 'w') as f:
        f.write('''#ifndef __GENERATED_WRAPPERS_HPP
#define __GENERATED_WRAPPERS_HPP

// AUTOGENERATED - DO NOT MODIFY

#include "../fmacros.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "wrappers.h"
#include "custom_matchers.hpp"
using namespace ::testing;
using ::testing::StrictMock;

#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
    // Older versions and transition versions have 'TestCase' naming available.
    #define SetUpTestSuite SetUpTestCase
    #define TearDownTestSuite TearDownTestCase
#endif

extern "C" {
''')

        for m in methods:
            f.write("    extern %s __real_%s(%s);\n" % (m.ret_type, m.name, m.full_args))

        f.write('''
}

class Valkey {
    public:
        virtual ~Valkey() {};
''')

        for m in methods:
            f.write("        virtual %s %s(%s) = 0;\n" % (m.ret_type, m.name, m.arg_string))

        f.write('''
};

class RealValkey : public Valkey {
    public:
        virtual ~RealValkey() {};
''')

        for m in methods:
            f.write('''
        virtual %s %s(%s) {
            %s__real_%s(%s);
        }''' % (m.ret_type, m.name, m.arg_string, 'return ' if m.ret_type != 'void' else '', m.name, ','.join(x.name for x in m.args)))

        f.write('''
};

class MockValkey : public Valkey {
    protected:
        RealValkey _real;

    public:
        MockValkey();
        virtual ~MockValkey();
''')

        for m in methods:
            f.write("        MOCK_METHOD%d(%s, %s(%s));\n" % (len(m.args), m.name, m.ret_type, m.arg_string))

        f.write('''};
#endif
''')


def generate_cpp(cpp_file, methods):
    with open(cpp_file, 'w') as f:
        f.write('''// AUTOGENERATED - DO NOT MODIFY

#include "generated_wrappers.hpp"

// A global pointer to our current test's MockValkey
// so that the C valkey wrappers can delegate to the
// correct mock
MockValkey* globalValkey = NULL;

extern "C" {
''')

        for m in methods:
            prefix = 'return ' if m.ret_type != 'void' else ''
            names = ','.join(x.name for x in m.args)
            f.write('''
    %s __wrap_%s(%s) {
        // Delegate to the global valkey if it is initialized
        if (globalValkey != NULL) {
            %sglobalValkey->%s(%s);
        } else {
            %s__real_%s(%s);
        }
    }''' % (m.ret_type, m.name, m.full_args, prefix, m.name, names, prefix, m.name, names))

        f.write('''
}

MockValkey::MockValkey() {
    // Set the global valkey to the current
    // instance
    globalValkey = this;
''')
        for m in methods:
            args = ','.join('_' for x in m.args)
            f.write("    EXPECT_CALL(*globalValkey, %s(%s)).WillRepeatedly(Invoke(&_real, &RealValkey::%s));\n" % (m.name, args, m.name))

        f.write('''
}

MockValkey::~MockValkey() {
    // Unset the global valkey
    globalValkey = NULL;
}
''')

def main():
    # Determine the output directory
    output_dir = "" if len(sys.argv) == 1 else f"{sys.argv[1]}/"

    # Parse the source file containing the wrappers
    methods = find_wrapper_functions_in_header('wrappers.h')

    # Do not generate the file if not needed
    generated_header = f"{output_dir}generated_wrappers.hpp"
    generated_cpp = f"{output_dir}generated_wrappers.cpp"

    generated_output_last_modified = 0 if not os.path.exists(generated_header) else os.path.getmtime(generated_header)
    input_last_modified = os.path.getmtime('wrappers.h')

    if generated_output_last_modified > input_last_modified:
        # Output files are up-to-date
        print(f"{generated_header} is up-to-date")
        print(f"{generated_cpp} is up-to-date")
        os.sys.exit(0)

    generate_header(generated_header, methods)
    generate_cpp(generated_cpp, methods)

if __name__ == "__main__":
    main()
