///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////

#ifdef _MSC_VER
    #pragma warning(disable: 4530 4548 4509)
    #pragma warning(disable: 6320)  // Exception-filter expression is the constant EXCEPTION_EXECUTE_HANDLER.
    #pragma warning(disable: 4472 4355)  // additional warnings generated by XDK with VS2015
#endif

#include <EAAssert/eaassert.h>
#include <EAMain/EAMain.h>
#include <eathread/eathread.h>
#include <eathread/eathread_atomic.h>
#ifdef EA_PLATFORM_ANDROID
    #include <eathread/eathread_futex.h>
#endif
#include <EAStdC/EAString.h>
#include <EAStdC/EASprintf.h>
#include <EAStdC/EADateTime.h>
#include <EAStdC/EAProcess.h>
#include <EAMain/internal/EAMainStartupShutdown.h>
#include <EAMain/internal/EAMainPrintManager.h>


#include <EABase/eabase.h>

EA_DISABLE_ALL_VC_WARNINGS()

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <stdarg.h>

#if defined(EA_PLATFORM_MICROSOFT)
        #ifndef WIN32_LEAN_AND_MEAN
            #define WIN32_LEAN_AND_MEAN
        #endif
        #include <Windows.h>
#elif defined(__APPLE__)    // OS X, iPhone, iPad, etc.
    #include <stdbool.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/sysctl.h>
    #import <mach/mach.h>
    #import <mach/mach_host.h>
#elif defined(EA_PLATFORM_BSD)
    #include <sys/types.h>
    #include <sys/ptrace.h>
#elif defined(EA_HAVE_SYS_PTRACE_H)
    #include <unistd.h>
    #include <sys/ptrace.h>
#elif defined(EA_PLATFORM_ANDROID)
    #include <unistd.h>
    #include <android/log.h>
#endif

EA_RESTORE_ALL_VC_WARNINGS()

///////////////////////////////////////////////////////////////////////////////
// EA_COMPILER_VA_COPY_REQUIRED
//
// This is already present in EABase version >= 2.00.40a report may not cause a flush
// See EABase for documentation.
//
#ifndef EA_COMPILER_VA_COPY_REQUIRED
    #if (EABASE_VERSION_N < 20040) // If not already handled by EABase...
        #if   ((defined(__GNUC__) && (__GNUC__ >= 3)) || defined(__clang__)) && (!defined(__i386__) || defined(__x86_64__)) && !defined(__ppc__) && !defined(__PPC__) && !defined(__PPC64__)
            #define EA_COMPILER_VA_COPY_REQUIRED 1
        #endif
    #endif
#endif


namespace EA
{
    namespace EAMain
    {
        namespace Internal
        {
            EAMAIN_API EAMainFunction gEAMainFunction;

            /// ReportDefault
            ///
            /// This is the default report function.
            /// It does not append any newlines to the output nor does it require
            /// the user to do so. It simply passes on the input to stdout.
            /// If the user wants the output to have a newline, the user must supply it.
            /// This allows the user to report multiple text items to the same line if desired.
            /// It does not send the input to stderr, as the output of a unit test
            /// is deemed to be test results (success or failure) and not errors.
            ///
#ifdef EA_PLATFORM_ANDROID
            static const size_t ANDROID_REPORT_BUFFER_SIZE = 1023;
            static char gAndroidReportBuffer[ANDROID_REPORT_BUFFER_SIZE + 1];
            static char *gAndroidReportBufferWritePtr = gAndroidReportBuffer;
            static EA::Thread::Futex gBufferFutex;
            static EA::Thread::ThreadTime gLastThreadTime = EA::Thread::kTimeoutImmediate;
            static EA::Thread::ThreadTime gMinTimeBetweenPrints = EA::Thread::ThreadTime(1);

            // This function assumes that the buffer futex above is held
            // prior to entry. Please ensure that the buffer futex is either
            // held by the calling code.
            static void FlushAndroidReportBuffer()
            {
                if (gAndroidReportBufferWritePtr != gAndroidReportBuffer)
                {
                    *gAndroidReportBufferWritePtr = 0;
                    __android_log_write(ANDROID_LOG_INFO, "EAMain", gAndroidReportBuffer);

                    // We found that if the OS is spammed too quickly with log info, it stops taking output from the app
                    // because it thinks it's a DDOS attack.  So we sleep to give the OS time in the case the last
                    // log output was too recent.
                    EA::Thread::ThreadTime currentTime = EA::Thread::GetThreadTime();
                    if (gLastThreadTime != EA::Thread::kTimeoutImmediate &&
                        ((currentTime - gLastThreadTime) < gMinTimeBetweenPrints))
                    {
                        EA::Thread::ThreadSleep(gMinTimeBetweenPrints);
                    }
                    gLastThreadTime = currentTime;

                    gAndroidReportBufferWritePtr = gAndroidReportBuffer;
                }
            }

            static void AppendToReportBuffer(char8_t c)
            {
                char *ptr = gAndroidReportBufferWritePtr;
                char *end = &gAndroidReportBuffer[ANDROID_REPORT_BUFFER_SIZE];

                if (ptr >= end)
                {
                    FlushAndroidReportBuffer();
                }

                *gAndroidReportBufferWritePtr++ = c;
            }

            static void AndroidReport(const char8_t *pMessage)
            {
                using namespace EA::StdC;
                using namespace EA::Thread;

                AutoFutex autoFutex(gBufferFutex);

                size_t messageLength = Strlen(pMessage);
                for (size_t i = 0; i < messageLength; ++i)
                {
                    char8_t c = pMessage[i];

                    switch (c)
                    {
                        case '\n':
                            FlushAndroidReportBuffer();
                            break;
                        default:
                            AppendToReportBuffer(c);
                            break;
                    }
                }
            }
#endif

            static void ReportDefault(const char8_t* pMessage)
            {
                if (!pMessage)
                {
                    return;
                }

                // It's possible that the underlying print back end can't handle large
                // output sizes. For example, the OutputDebugStringA call below drops
                // chars beyond about 4096.
                size_t       length = EA::StdC::Strlen(pMessage); // It might be faster to make a custom Strlen which quits after N chars.
                const size_t kMaxLength = 1024;

                if(length > kMaxLength)
                {
                    for(size_t i = 0, copiedLength = 0; i < length; i += copiedLength)
                    {
                        char8_t buffer[kMaxLength + 1];
                        size_t  c;

                        copiedLength = ((length - i) >= kMaxLength) ? kMaxLength : (length - i);
                        for(c = 0; c < copiedLength; c++)
                            buffer[c] = pMessage[i + c];
                        buffer[c] = 0;
                        ReportDefault(buffer);
                    }
                }
                else
                {
                    #if defined(EA_PLATFORM_MICROSOFT) && !defined(CS_UNDEFINED_STRING) // No need to do this for Microsoft console platforms, as the fputs below covers that.
                        OutputDebugStringA(pMessage);
                    #endif

                    #if defined(EA_PLATFORM_ANDROID)
                        // Android doesn't implement stdio (e.g. fputs), though sometimes we use compiler
                        // linking statements to redirect stdio functions to our own implementations which
                        // allow it to work.
                        //
                        // __android_log_write can write only 512 bytes at a time. Normally we don't write
                        // so much text in unit test output, but if this becomes a problem then we can loop
                        // and write blocks of the output. The primary downside to such an approach is that
                        // __android_log_write appends a \n to your output for each call. See the EAStdC
                        // EASprintfCore.cpp code for example loop code.
                        AndroidReport(pMessage);
                    #else
                        fputs(pMessage, stdout);
                        fflush(stdout);
                    #endif
                }
            }

            EAMAIN_API const char *ExtractPrintServerAddress(int argc, char **argv)
            {
                CommandLine commandLine(argc, argv);
                const char *printServerAddress = NULL;

                if (commandLine.FindSwitch("PrintServerIPAddress", false, &printServerAddress, 0, '=') >= 0)
                {
                    if (EA::StdC::Strlen(printServerAddress) > 0)
                    {
                        return printServerAddress;
                    }
                }

                return NULL;
            }
        }

        ReportFunction gpReportFunction = EA::EAMain::Internal::ReportDefault;
        EAMAIN_API void SetReportFunction(ReportFunction pReportFunction)
        {
            gpReportFunction = pReportFunction;
        }

        EAMAIN_API ReportFunction GetReportFunction()
        {
            return gpReportFunction;
        }

        EAMAIN_API ReportFunction GetDefaultReportFunction()
        {
            using namespace EA::EAMain::Internal;

            return ReportDefault;
        }
        ///////////////////////////////////////////////////////////////////////////////
        // GetVerbosity / SetVerbosity
        ///////////////////////////////////////////////////////////////////////////////

        unsigned gVerbosity = 0; // 0 means to display just failures.

        EAMAIN_API unsigned GetVerbosity()
        {
            return gVerbosity;
        }


        EAMAIN_API void SetVerbosity(unsigned verbosity)
        {
            gVerbosity = verbosity;
        }

        ///////////////////////////////////////////////////////////////////////////////
        // ReportVaList
        //
        static void ReportVaList(unsigned minVerbosity, ReportFunction pReportFunction, const char8_t* pFormat, va_list arguments)
        {
            if(pFormat && (GetVerbosity() >= minVerbosity))
            {
                #if defined(EA_PLATFORM_DESKTOP)
                const int kBufferSize = 2048;
                #else
                const int kBufferSize = 512;
                #endif
                char buffer[kBufferSize];

                #if defined(EA_COMPILER_VA_COPY_REQUIRED)
                    va_list argumentsSaved;
                    va_copy(argumentsSaved, arguments);
                #endif

                const int nReturnValue = EA::StdC::Vsnprintf(buffer, kBufferSize, pFormat, arguments);

                if(!pReportFunction)
                    pReportFunction = gpReportFunction;

                if(pReportFunction)
                {
                    if((nReturnValue >= 0) && (nReturnValue < (int)kBufferSize))
                        pReportFunction(buffer);
                    else if(nReturnValue < 0) // If we simply didn't have enough buffer space.
                    {
                        pReportFunction("Invalid format specified.\n    Format: ");
                        pReportFunction(pFormat);
                    }
                    else // Else we simply didn't have enough buffer space.
                    {
                        char* pBuffer = static_cast<char *>(calloc(nReturnValue + 1, 1));

                        if(pBuffer)
                        {
                            #if defined(EA_COMPILER_VA_COPY_REQUIRED)
                                va_end(arguments);
                                va_copy(arguments, argumentsSaved);
                            #endif

                            EA::StdC::Vsnprintf(pBuffer, nReturnValue + 1, pFormat, arguments);
                            pReportFunction(pBuffer);
                            free(pBuffer);
                        }
                        else
                            pReportFunction("Unable to allocate buffer space for large printf.\n");
                    }
                }

                #if defined(EA_COMPILER_VA_COPY_REQUIRED)
                    // The caller will call va_end(arguments)
                    va_end(argumentsSaved);
                #endif
            }
        }

        ///////////////////////////////////////////////////////////////////////////////
        // Report
        //
        EAMAIN_API void Report(const char8_t* pFormat, ...)
        {
            va_list arguments;
            va_start(arguments, pFormat);
            ReportVaList(0, gpReportFunction, pFormat, arguments);
            va_end(arguments);
        }

        ///////////////////////////////////////////////////////////////////////////////
        // ReportVerbosity
        //
        EAMAIN_API void ReportVerbosity(unsigned minVerbosity, const char8_t* pFormat, ...)
        {
            va_list arguments;
            va_start(arguments, pFormat);
            ReportVaList(minVerbosity, gpReportFunction, pFormat, arguments);
            va_end(arguments);
        }

        ///////////////////////////////////////////////////////////////////////////////
        // VReport
        //
        EAMAIN_API void VReport(const char8_t* pFormat, va_list arguments)
        {
            ReportVaList(0, gpReportFunction, pFormat, arguments);
        }

        ///////////////////////////////////////////////////////////////////////////////
        // VReportVerbosity
        //
        EAMAIN_API void VReportVerbosity(unsigned minVerbosity, const char8_t* pFormat, va_list arguments)
        {
            ReportVaList(minVerbosity, gpReportFunction, pFormat, arguments);
        }

        ///////////////////////////////////////////////////////////////////////////////
        // PlatformStartup
        //
        EAMAIN_API void PlatformStartup()
        {
            // Routed to EAMainStartup to centralize
            // the platform specific startup code.
            PlatformStartup(NULL);
        }

        EAMAIN_API void PlatformStartup(int argc, char **argv)
        {
            const char *printServerNetworkAddress = Internal::ExtractPrintServerAddress(argc, argv);

            PlatformStartup(printServerNetworkAddress);
        }

        EAMAIN_API void PlatformStartup(const char *printServerNetworkAddress)
        {
            // Routed to EAMainStartup to centralize
            // the platform specific startup code.
            EA::EAMain::Internal::EAMainStartup(printServerNetworkAddress);
        }

        ///////////////////////////////////////////////////////////////////////////////
        // PlatformShutdown
        //
        EAMAIN_API void PlatformShutdown(int errorCount)
        {
#ifdef EA_PLATFORM_ANDROID
            // The Android reporting functions will flush the output buffers
            // when a newline is encountered. Calling AndroidReport with a
            // single newline will cause any accumulated output to flush to
            // the log.
            //
            // An alternative would be to call the FlushAndroidReportBuffer
            // function but doing so would necessitate having separate locks
            // for both FlushAndroidReportBuffer and AndroidReport, as both
            // of these could be caused at the same time. To avoid this
            // complication, FlushAndroidReportBuffer will only be called by
            // AndroidReport or its children.
            // -mburke
            Internal::AndroidReport("\n");
#endif
            // Routed to EAMainShutdown to centralize
            // the platform specific shutdown code.
            EA::EAMain::Internal::EAMainShutdown(errorCount);
        }

        ///////////////////////////////////////////////////////////////////////////////
        // CommandLine
        ///////////////////////////////////////////////////////////////////////////////
        CommandLine::CommandLine(int argc, char** argv)
            : mArgc(argc)
            , mArgv(NULL)
            , mCommandLine(NULL)
        {
            mArgv = static_cast<char **>(calloc(argc + 1, sizeof(char *)));
            EA_ASSERT(mArgv != NULL);

            for (int i = 0; i < argc; ++i)
            {
                mArgv[i] = argv[i];
            }

            mArgv[argc] = NULL;

            // Microsoft fails to support argc/argv on Xenon. Sometimes it works; sometimes it doesn't.
        }

        CommandLine::CommandLine(const char *args)
            : mArgc(0),
              mArgv(NULL),
              mCommandLine(NULL)
        {
            ParseCommandLine(args, FLAG_NONE);
        }

        CommandLine::CommandLine(const char *args, unsigned int flags)
            : mArgc(0),
              mArgv(NULL),
              mCommandLine(NULL)
        {
            ParseCommandLine(args, flags);
        }


        CommandLine::~CommandLine()
        {
            if (mArgv)
            {
                free(mArgv);
                mArgv = NULL;
            }

            if (mCommandLine)
            {
                free(mCommandLine);
                mCommandLine = NULL;
            }
        }

        /// Stristr
        /// We implement this here because it isn't consistently present with all compiler-supplied C libraries.
        static char* Stristr(const char* s1, const char* s2)
        {
            const char* cp = s1;

            if(!*s2)
                return (char*)s1;

            while(*cp)
            {
                const char* s = cp;
                const char* t = s2;

                while(*s && *t && (tolower(*s) == tolower(*t)))
                    ++s, ++t;

                if(*t == 0)
                    return (char*)cp;
                ++cp;
            }

            return 0;
        }

        //Returns position switch is found at. Returns -1 if not found
        int CommandLine::FindSwitch(const char* pSwitch, bool bCaseSensitive, const char** pResult, int nStartingIndex, char delimeter) const
        {
            const char8_t kSwitchIDs[]   = { '-', '/' };
            const int     kSwitchIDCount = sizeof(kSwitchIDs)/sizeof(kSwitchIDs[0]);
            static const char sEmptyString[] = { 0 };

            if(nStartingIndex < 0)
            {
                nStartingIndex = 0;
            }

            if (pResult)
            {
                *pResult = sEmptyString;
            }

            // Here we move the input pSwitch past any one leading switch indicator such as '-'.
            for(int i = 0; i < kSwitchIDCount; ++i)
            {
                if(*pSwitch == kSwitchIDs[i])
                {
                    ++pSwitch;
                    break;
                }
            }

            const size_t nSwitchLength = strlen(pSwitch);

            if (!nSwitchLength || (nStartingIndex >= mArgc))
                return -1;

            for(int i = nStartingIndex; i < mArgc; ++i)
            {
                const char *sCurrent = mArgv[i];

                if(strlen(sCurrent) >= 2) // Enough, for example, for "-x".
                {
                    int j;

                    // Make sure the string starts with a switch ID (e.g. '-').
                    for(j = 0; j < kSwitchIDCount; ++j)
                    {
                        if(sCurrent[0] == kSwitchIDs[j])
                            break;
                    }

                    if(j < kSwitchIDCount) // If a leading '-' was found...
                    {
                        const char* pCurrent = bCaseSensitive ? strstr(sCurrent + 1, pSwitch) : Stristr(sCurrent + 1, pSwitch);
                        const char* pCStr    = sCurrent;

                        if(pCurrent == (pCStr + 1)) // If the user's input switch matched at least the start of the current argument switch...
                        {
                            pCurrent += nSwitchLength; // Move pCurrent past the input switch.

                            // At this point, we require that *pCurrent is either 0 or delimeter.
                            if((*pCurrent == 0) || (*pCurrent == delimeter))
                            {
                                // We have a match. Now possibly return a result string.
                                if(*pCurrent == delimeter)
                                {
                                    if(*++pCurrent)
                                    {
                                        if(pResult)
                                        {
                                            *pResult = pCurrent;
                                        }
                                    }
                                }

                                return i;
                            }
                        }
                    }
                }
            }

            return -1;
        }

        bool CommandLine::HasHelpSwitch() const
        {
            if((FindSwitch("-help", false, NULL, 0) >= 0) ||
               (FindSwitch("-h", false, NULL, 0) >= 0) ||
               (FindSwitch("-?", false, NULL, 0) >= 0))
            {
                return true;
            }
                return false;
        }

        void CommandLine::ParseCommandLine(const char *inputCommandLine, unsigned int flags)
        {
            size_t commandLineLength = strlen(inputCommandLine);
            size_t allocSize = commandLineLength + 1;
            size_t startOffset = 0;

            if (flags & FLAG_NO_PROGRAM_NAME)
            {
                allocSize += 1;
                startOffset = 1;
            }

            char *commandLine = static_cast<char *>(calloc(allocSize, 1));
            EA_ASSERT(commandLine != NULL);

            memcpy(commandLine + startOffset, inputCommandLine, commandLineLength);

            int argc = 0;
            char **argv = static_cast<char **>(calloc(MAX_COMMANDLINE_ARGS, sizeof(char *)));
            EA_ASSERT(argv != NULL);

            char *start = commandLine + startOffset;
            char *ptr = start;
            char *end = start + commandLineLength;
            bool isQuoted = false;
            const char quote = '"';

            if (flags & FLAG_NO_PROGRAM_NAME)
            {
                argv[argc++] = commandLine;
            }

            while (ptr < end)
            {
                // The two cases this parser handles for quotes are:
                //   "this is a quoted parameter"; and
                //   -D:"this is a quoted parameter"
                // The parser does not handle edge cases like
                //   "this is a quoted parameter"and"this is the same"
                char *quoteStart = NULL;

                for (;;)
                {
                    while ((ptr < end) && !isspace((unsigned char)*ptr))
                    {
                        if (*ptr == quote && !isQuoted)
                        {
                            isQuoted = true;
                            quoteStart = ptr;
                        }

                        ++ptr;
                    }

                    if (isQuoted)
                    {
                        if (*(ptr - 1) == quote)
                        {
                            // If we find a quote, shift the whole string back
                            // by one character, ie:
                            //   -D:"this is a quoted parameter"
                            // becomes
                            //   -D:this is a quoted parameter"
                            // The trailing quote is removed below when we place
                            // a null terminator at the end of our argument.
                            memmove(quoteStart, quoteStart + 1, (end - quoteStart));
                            --end;
                            ptr -= 2;
                            isQuoted = false;
                            break;
                        }
                        ++ptr;
                    }
                    else
                    {
                        break;
                    }
                }

                if (ptr != start)
                {
                    *ptr = 0;

                    argv[argc++] = start;
                    ++ptr;
                }

                while ((ptr < end) && isspace((unsigned char)*ptr))
                {
                    ++ptr;
                }

                start = ptr;
            }

            mArgc = argc;
            mArgv = argv;
            mCommandLine = commandLine;
        }
    }
}

// WinRT-based Windows:
#if (defined(EA_PLATFORM_MICROSOFT) && !defined(CS_UNDEFINED_STRING) && !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP))

EA_DISABLE_VC_WARNING(4350 4571 4625 4626 4265)
#include <future>
EA_RESTORE_ALL_VC_WARNINGS()

extern "C" int EAMain(int argc, char** argv);

namespace EA
{

namespace EAMain
{

class WinRTRunner : public IWinRTRunner
{
private:
    // The copy/assignment operator is explicitly inaccessible due to the base class 'IWinRTRunner' containing a member
    // that is  non-copyable (eg.  std::future).
    WinRTRunner(const WinRTRunner &);
    WinRTRunner& operator=(const WinRTRunner &);

public:
    WinRTRunner() {}

    virtual void Run(int argc, char** argv) override
    {
        mResult = std::async(std::launch::async, [=]() {

            const char *printServerAddress = Internal::ExtractPrintServerAddress(argc, argv);
            EA::EAMain::Internal::EAMainStartup(printServerAddress);

            int result = EA::EAMain::Internal::gEAMainFunction(argc, argv);
            EA::EAMain::Internal::EAMainShutdown(result);
            return result;
        });
    }

    virtual bool IsFinished() override { return mResult.wait_for(std::chrono::milliseconds(33)) == std::future_status::ready; }

    virtual void ReportResult() override
    {
        char output[100];
        EA::StdC::Snprintf(output, EAArrayCount(output), "EXIT(%d)\n", mResult.get());

        // Using OutputDebugStringA directly here as opposed to Report as someone may overload
        // the default reporter. And this is what counts for EARunner to know what to do.
        OutputDebugStringA(output);
    }

    std::future<int> mResult;
};

EAMAIN_API IWinRTRunner* CreateWinRTRunner()
{
    return new WinRTRunner();
}

} // namespace EAMain

} // namespace EA

#endif // WinRT-based Windows
