switch (currentOrientation) {
                case DisplayOrientations::Landscape:
                case DisplayOrientations::LandscapeFlipped: {
                    int tmp = w;
                    w = h;
                    h = tmp;
                } break;
            }

/*
  Simple DirectMedia Layer

*/
#include "../../SDL_internal.h"

/* Standard C++11 includes */
#include <functional>
#include <string>
#include <sstream>
// rbfx fix

/* Windows includes */
#include "ppltasks.h"
using namespace concurrency;
using namespace Windows::ApplicationModel;
using namespace Windows::ApplicationModel::Core;
using namespace Windows::ApplicationModel::Activation;
using namespace Windows::Devices::Input;
using namespace Windows::Graphics::Display;
using namespace Windows::Foundation;
using namespace Windows::System;
using namespace Windows::UI::Core;
using namespace Windows::UI::Input;

#if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
using namespace Windows::Phone::UI::Input;
#endif

/* SDL includes */
extern "C" {
#include "SDL_assert.h"
#include "SDL_events.h"
#include "SDL_hints.h"
#include "SDL_log.h"
#include "SDL_main.h"
#include "SDL_stdinc.h"
#include "SDL_render.h"
#include "../../video/SDL_sysvideo.h"
#include "../../events/SDL_events_c.h"
#include "../../events/SDL_keyboard_c.h"
#include "../../events/SDL_mouse_c.h"
#include "../../events/SDL_windowevents_c.h"
#include "../../render/SDL_sysrender.h"
#include "../windows/SDL_windows.h"
}

#include "../../video/winrt/SDL_winrtevents_c.h"
#include "../../video/winrt/SDL_winrtvideo_cpp.h"
#include "SDL_winrtapp_common.h"
#include "SDL_winrtapp_direct3d.h"

#if SDL_VIDEO_RENDER_D3D11 && !SDL_RENDER_DISABLED
/* Calling IDXGIDevice3::Trim on the active Direct3D 11.x device is necessary
 * when Windows 8.1 apps are about to get suspended.
 */
extern "C" void D3D11_Trim(SDL_Renderer *);
#endif

// Compile-time debugging options:
// To enable, uncomment; to disable, comment them out.

// HACK, DLudwig: record a reference to the global, WinRT 'app'/view.
// SDL/WinRT will use this throughout its code.
// TODO, WinRT: consider replacing SDL_WinRTGlobalApp with something
// non-global, such as something created inside
// SDL_InitSubSystem(SDL_INIT_VIDEO), or something inside
SDL_WinRTApp ^ SDL_WinRTGlobalApp = nullptr;

ref class SDLApplicationSource sealed : Windows::ApplicationModel::Core::IFrameworkViewSource
{
public:
    virtual Windows::ApplicationModel::Core::IFrameworkView^ CreateView();
};

IFrameworkView^ SDLApplicationSource::CreateView()
{
    // TODO, WinRT: see if this function (CreateView) can ever get called
    // more than once.  For now, just prevent it from ever assigning
    // SDL_WinRTGlobalApp more than once.
    SDL_assert(!SDL_WinRTGlobalApp);
    SDL_WinRTApp ^ app = ref new SDL_WinRTApp();
    if (!SDL_WinRTGlobalApp)
    {
        SDL_WinRTGlobalApp = app;
    }
    return app;
}

int SDL_WinRTInitNonXAMLApp(int (*mainFunction)(int, char **))
{
    WINRT_SDLAppEntryPoint = mainFunction;
    auto direct3DApplicationSource = ref new SDLApplicationSource();
    CoreApplication::Run(direct3DApplicationSource);
    return 0;
}

static void SDLCALL
WINRT_SetDisplayOrientationsPreference(void *userdata, const char *name, const char *oldValue, const char *newValue)
{
    SDL_assert(SDL_strcmp(name, SDL_HINT_ORIENTATIONS) == 0);

    /* HACK: prevent SDL from altering an app's .appxmanifest-set orientation
     * from being changed on startup, by detecting when SDL_HINT_ORIENTATIONS
     * is getting registered.
     *
     * TODO, WinRT: consider reading in an app's .appxmanifest file, and apply its orientation when 'newValue == NULL'.
     */
    if ((oldValue == NULL) && (newValue == NULL)) {
        return;
    }

    // Start with no orientation flags, then add each in as they're parsed
    unsigned int orientationFlags = 0;
    if (newValue) {
        std::istringstream tokenizer(newValue);
        while (!tokenizer.eof()) {
            std::string orientationName;
            std::getline(tokenizer, orientationName, ' ');
            if (orientationName == "LandscapeLeft") {
                orientationFlags |= (unsigned int) DisplayOrientations::LandscapeFlipped;
            } else if (orientationName == "LandscapeRight") {
                orientationFlags |= (unsigned int) DisplayOrientations::Landscape;
            } else if (orientationName == "Portrait") {
                orientationFlags |= (unsigned int) DisplayOrientations::Portrait;
            } else if (orientationName == "PortraitUpsideDown") {
                orientationFlags |= (unsigned int) DisplayOrientations::PortraitFlipped;
            }
        }
    }

    // If no valid orientation flags were specified, use a reasonable set of defaults:
    if (!orientationFlags) {
        // TODO, WinRT: consider seeing if an app's default orientation flags can be found out via some API call(s).
        orientationFlags = (unsigned int) ( \
            DisplayOrientations::Landscape |
            DisplayOrientations::LandscapeFlipped |
            DisplayOrientations::Portrait |
            DisplayOrientations::PortraitFlipped);
    }

    // Set the orientation/rotation preferences.  Please note that this does
    // not constitute a 100%-certain lock of a given set of possible
    // orientations.  According to Microsoft's documentation on WinRT [1]
    // when a device is not capable of being rotated, Windows may ignore
    // the orientation preferences, and stick to what the device is capable of
    // [1] Documentation on the 'InitialRotationPreference' setting for a
    // Windows app's manifest file describes how some orientation/rotation
    // preferences may be ignored.  See
    // for details.  Microsoft's "Display orientation sample" also gives an
    // outline of how Windows treats device rotation
    WINRT_DISPLAY_PROPERTY(AutoRotationPreferences) = (DisplayOrientations) orientationFlags;
}

static void
WINRT_ProcessWindowSizeChange() // TODO: Pass an SDL_Window-identifying thing into WINRT_ProcessWindowSizeChange()
{
    CoreWindow ^ coreWindow = CoreWindow::GetForCurrentThread();
    if (coreWindow) {
        if (WINRT_GlobalSDLWindow) {
            SDL_Window * window = WINRT_GlobalSDLWindow;
            SDL_WindowData * data = (SDL_WindowData *) window->driverdata;

            int x = WINRT_DIPS_TO_PHYSICAL_PIXELS(data->coreWindow->Bounds.Left);
            int y = WINRT_DIPS_TO_PHYSICAL_PIXELS(data->coreWindow->Bounds.Top);
            int w = WINRT_DIPS_TO_PHYSICAL_PIXELS(data->coreWindow->Bounds.Width);
            int h = WINRT_DIPS_TO_PHYSICAL_PIXELS(data->coreWindow->Bounds.Height);

#if (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) && (NTDDI_VERSION == NTDDI_WIN8)
            /* WinPhone 8.0 always keeps its native window size in portrait,
               regardless of orientation.  This changes in WinPhone 8.1,
               in which the native window's size changes along with
               orientation.

               Attempt to emulate WinPhone 8.1's behavior on WinPhone 8.0, with
               regards to window size.  This fixes a rendering bug that occurs
               when a WinPhone 8.0 app is rotated to either 90 or 270 degrees.
            */
            const DisplayOrientations currentOrientation = WINRT_DISPLAY_PROPERTY(CurrentOrientation);

#endif

            const Uint32 latestFlags = WINRT_DetectWindowFlags(window);
            if (latestFlags & SDL_WINDOW_MAXIMIZED) {
                SDL_SendWindowEvent(window, SDL_WINDOWEVENT_MAXIMIZED, 0, 0);
            } else {
                SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESTORED, 0, 0);
            }

            WINRT_UpdateWindowFlags(window, SDL_WINDOW_FULLSCREEN_DESKTOP);

            /* The window can move during a resize event, such as when maximizing
               or resizing from a corner */
            SDL_SendWindowEvent(window, SDL_WINDOWEVENT_MOVED, x, y);
            SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, w, h);
        }
    }
}

SDL_WinRTApp::SDL_WinRTApp() :
    m_windowClosed(false),
    m_windowVisible(true)
{
}

void SDL_WinRTApp::Initialize(CoreApplicationView^ applicationView)
{
    applicationView->Activated +=
        ref new TypedEventHandler<CoreApplicationView^, IActivatedEventArgs^>(this, &SDL_WinRTApp::OnAppActivated);

    CoreApplication::Suspending +=
        ref new EventHandler<SuspendingEventArgs^>(this, &SDL_WinRTApp::OnSuspending);

    CoreApplication::Resuming +=
        ref new EventHandler<Platform::Object^>(this, &SDL_WinRTApp::OnResuming);

    CoreApplication::Exiting +=
        ref new EventHandler<Platform::Object^>(this, &SDL_WinRTApp::OnExiting);

#if NTDDI_VERSION >= NTDDI_WIN10
    /* HACK ALERT!  Xbox One doesn't seem to detect gamepads unless something
       gets registered to receive Win10's Windows.Gaming.Input.Gamepad.GamepadAdded
       events.  We'll register an event handler for these events here, to make
       sure that gamepad detection works later on, if requested.
    */
    Windows::Gaming::Input::Gamepad::GamepadAdded +=
        ref new Windows::Foundation::EventHandler<Windows::Gaming::Input::Gamepad^>(
            this, &SDL_WinRTApp::OnGamepadAdded
        );
#endif
}

#if NTDDI_VERSION > NTDDI_WIN8
void SDL_WinRTApp::OnOrientationChanged(DisplayInformation^ sender, Object^ args)
#else
void SDL_WinRTApp::OnOrientationChanged(Object^ sender)
#endif
{
#if LOG_ORIENTATION_EVENTS==1
    {
        CoreWindow^ window = CoreWindow::GetForCurrentThread();
        if (window) {
            SDL_Log("%s, current orientation=%d, native orientation=%d, auto rot. pref=%d, CoreWindow Bounds={%f,%f,%f,%f}\n",
                __FUNCTION__,
                WINRT_DISPLAY_PROPERTY(CurrentOrientation),
                WINRT_DISPLAY_PROPERTY(NativeOrientation),
                WINRT_DISPLAY_PROPERTY(AutoRotationPreferences),
                window->Bounds.X,
                window->Bounds.Y,
                window->Bounds.Width,
                window->Bounds.Height);
        } else {
            SDL_Log("%s, current orientation=%d, native orientation=%d, auto rot. pref=%d\n",
                __FUNCTION__,
                WINRT_DISPLAY_PROPERTY(CurrentOrientation),
                WINRT_DISPLAY_PROPERTY(NativeOrientation),
                WINRT_DISPLAY_PROPERTY(AutoRotationPreferences));
        }
    }
#endif

    WINRT_ProcessWindowSizeChange();

#if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
    // HACK: Make sure that orientation changes
    // lead to the Direct3D renderer's viewport getting updated:
    // For some reason, this doesn't seem to need to be done on Windows 8.x,
    // even when going from Landscape to LandscapeFlipped.  It only seems to
    // be needed on Windows Phone, at least when I tested on my devices.
    // I'm not currently sure why this is, but it seems to work fine. -- David L.
    // TODO, WinRT: do more extensive research into why orientation changes on Win 8.x don't need D3D changes, or if they might, in some cases
    SDL_Window * window = WINRT_GlobalSDLWindow;
    if (window) {
        SDL_WindowData * data = (SDL_WindowData *)window->driverdata;
        int w = WINRT_DIPS_TO_PHYSICAL_PIXELS(data->coreWindow->Bounds.Width);
        int h = WINRT_DIPS_TO_PHYSICAL_PIXELS(data->coreWindow->Bounds.Height);
        SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_SIZE_CHANGED, w, h);
    }
#endif

}

void SDL_WinRTApp::SetWindow(CoreWindow^ window)
{
#if LOG_WINDOW_EVENTS==1
    SDL_Log("%s, current orientation=%d, native orientation=%d, auto rot. pref=%d, window bounds={%f, %f, %f,%f}\n",
        __FUNCTION__,
        WINRT_DISPLAY_PROPERTY(CurrentOrientation),
        WINRT_DISPLAY_PROPERTY(NativeOrientation),
        WINRT_DISPLAY_PROPERTY(AutoRotationPreferences),
        window->Bounds.X,
        window->Bounds.Y,
        window->Bounds.Width,
        window->Bounds.Height);
#endif

    window->SizeChanged +=
        ref new TypedEventHandler<CoreWindow^, WindowSizeChangedEventArgs^>(this, &SDL_WinRTApp::OnWindowSizeChanged);

    window->VisibilityChanged +=
        ref new TypedEventHandler<CoreWindow^, VisibilityChangedEventArgs^>(this, &SDL_WinRTApp::OnVisibilityChanged);

    window->Activated +=
        ref new TypedEventHandler<CoreWindow^, WindowActivatedEventArgs^>(this, &SDL_WinRTApp::OnWindowActivated);

    window->Closed +=
        ref new TypedEventHandler<CoreWindow^, CoreWindowEventArgs^>(this, &SDL_WinRTApp::OnWindowClosed);

#if WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP
    window->PointerCursor = ref new CoreCursor(CoreCursorType::Arrow, 0);
#endif

    window->PointerPressed +=
        ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &SDL_WinRTApp::OnPointerPressed);

    window->PointerMoved +=
        ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &SDL_WinRTApp::OnPointerMoved);

    window->PointerReleased +=
        ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &SDL_WinRTApp::OnPointerReleased);

    window->PointerEntered +=
        ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &SDL_WinRTApp::OnPointerEntered);

    window->PointerExited +=
        ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &SDL_WinRTApp::OnPointerExited);

    window->PointerWheelChanged +=
        ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &SDL_WinRTApp::OnPointerWheelChanged);

#if WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP
    // Retrieves relative-only mouse movements:
    Windows::Devices::Input::MouseDevice::GetForCurrentView()->MouseMoved +=
        ref new TypedEventHandler<MouseDevice^, MouseEventArgs^>(this, &SDL_WinRTApp::OnMouseMoved);
#endif

    window->KeyDown +=
        ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(this, &SDL_WinRTApp::OnKeyDown);

    window->KeyUp +=
        ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(this, &SDL_WinRTApp::OnKeyUp);

    window->CharacterReceived +=
        ref new TypedEventHandler<CoreWindow^, CharacterReceivedEventArgs^>(this, &SDL_WinRTApp::OnCharacterReceived);

#if NTDDI_VERSION >= NTDDI_WIN10
    Windows::UI::Core::SystemNavigationManager::GetForCurrentView()->BackRequested +=
        ref new EventHandler<BackRequestedEventArgs^>(this, &SDL_WinRTApp::OnBackButtonPressed);
#elif WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
    HardwareButtons::BackPressed +=
        ref new EventHandler<BackPressedEventArgs^>(this, &SDL_WinRTApp::OnBackButtonPressed);
#endif

#if NTDDI_VERSION > NTDDI_WIN8
    DisplayInformation::GetForCurrentView()->OrientationChanged +=
        ref new TypedEventHandler<Windows::Graphics::Display::DisplayInformation^, Object^>(this, &SDL_WinRTApp::OnOrientationChanged);
#else
    DisplayProperties::OrientationChanged +=
        ref new DisplayPropertiesEventHandler(this, &SDL_WinRTApp::OnOrientationChanged);
#endif

    // Register the hint, SDL_HINT_ORIENTATIONS, with SDL.
    // TODO, WinRT: see if an app's default orientation can be found out via WinRT API(s), then set the initial value of SDL_HINT_ORIENTATIONS accordingly.
    SDL_AddHintCallback(SDL_HINT_ORIENTATIONS, WINRT_SetDisplayOrientationsPreference, NULL);

#if (WINAPI_FAMILY == WINAPI_FAMILY_APP) && (NTDDI_VERSION < NTDDI_WIN10)  // for Windows 8/8.1/RT apps... (and not Phone apps)
    // Make sure we know when a user has opened the app's settings pane.
    // This is needed in order to display a privacy policy, which needs
    // to be done for network-enabled apps, as per Windows Store requirements.
    using namespace Windows::UI::ApplicationSettings;
    SettingsPane::GetForCurrentView()->CommandsRequested +=
        ref new TypedEventHandler<SettingsPane^, SettingsPaneCommandsRequestedEventArgs^>
            (this, &SDL_WinRTApp::OnSettingsPaneCommandsRequested);
#endif
}

void SDL_WinRTApp::Load(Platform::String^ entryPoint)
{
}

void SDL_WinRTApp::Run()
{
    SDL_SetMainReady();
    if (WINRT_SDLAppEntryPoint)
    {
        // TODO, WinRT: pass the C-style main() a reasonably realistic
        // representation of command line arguments.
        int argc = 0;
        char **argv = NULL;
        WINRT_SDLAppEntryPoint(argc, argv);
    }
}

static bool IsSDLWindowEventPending(SDL_WindowEventID windowEventID)
{
    SDL_Event events[128];
    const int count = SDL_PeepEvents(events, sizeof(events)/sizeof(SDL_Event), SDL_PEEKEVENT, SDL_WINDOWEVENT, SDL_WINDOWEVENT);
    for (int i = 0; i < count; ++i) {
        if (events[i].window.event == windowEventID) {
            return true;
        }
    }
    return false;
}

bool SDL_WinRTApp::ShouldWaitForAppResumeEvents()
{
    /* Don't wait if the app is visible: */
    if (m_windowVisible) {
        return false;
    }

    /* Don't wait until the window-hide events finish processing.
     * Do note that if an app-suspend event is sent (as indicated
     * by SDL_APP_WILLENTERBACKGROUND and SDL_APP_DIDENTERBACKGROUND
     * events), then this code may be a moot point, as WinRT's
     * own event pump (aka ProcessEvents()) will pause regardless
     * of what we do here.  This happens on Windows Phone 8, to note.
     * Windows 8.x apps, on the other hand, may get a chance to run
     * these.
     */
    if (IsSDLWindowEventPending(SDL_WINDOWEVENT_HIDDEN)) {
        return false;
    } else if (IsSDLWindowEventPending(SDL_WINDOWEVENT_FOCUS_LOST)) {
        return false;
    } else if (IsSDLWindowEventPending(SDL_WINDOWEVENT_MINIMIZED)) {
        return false;
    }

    return true;
}

void SDL_WinRTApp::PumpEvents()
{
    if (!m_windowClosed) {
        if (!ShouldWaitForAppResumeEvents()) {
            /* This is the normal way in which events should be pumped.
             * 'ProcessAllIfPresent' will make ProcessEvents() process anywhere
             * from zero to N events, and will then return.
             */
            CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);
        } else {
            /* This style of event-pumping, with 'ProcessOneAndAllPending',
             * will cause anywhere from one to N events to be processed.  If
             * at least one event is processed, the call will return.  If
             * no events are pending, then the call will wait until one is
             * available, and will not return (to the caller) until this
             * happens!  This should only occur when the app is hidden.
             */
            CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
        }
    }
}

void SDL_WinRTApp::Uninitialize()
{
}

#if (WINAPI_FAMILY == WINAPI_FAMILY_APP) && (NTDDI_VERSION < NTDDI_WIN10)
void SDL_WinRTApp::OnSettingsPaneCommandsRequested(
    Windows::UI::ApplicationSettings::SettingsPane ^p,
    Windows::UI::ApplicationSettings::SettingsPaneCommandsRequestedEventArgs ^args)
{
    using namespace Platform;
    using namespace Windows::UI::ApplicationSettings;
    using namespace Windows::UI::Popups;

    String ^privacyPolicyURL = nullptr;     // a URL to an app's Privacy Policy
    String ^privacyPolicyLabel = nullptr;   // label/link text
    const char *tmpHintValue = NULL;        // SDL_GetHint-retrieved value, used immediately
    wchar_t *tmpStr = NULL;                 // used for UTF8 to UCS2 conversion

    // Setup a 'Privacy Policy' link, if one is available (via SDL_GetHint):
    tmpHintValue = SDL_GetHint(SDL_HINT_WINRT_PRIVACY_POLICY_URL);
    if (tmpHintValue && tmpHintValue[0] != '\0') {
        // Convert the privacy policy's URL to UCS2:
        tmpStr = WIN_UTF8ToString(tmpHintValue);
        privacyPolicyURL = ref new String(tmpStr);
        SDL_free(tmpStr);

        // Optionally retrieve custom label-text for the link.  If this isn't
        // available, a default value will be used instead.
        tmpHintValue = SDL_GetHint(SDL_HINT_WINRT_PRIVACY_POLICY_LABEL);
        if (tmpHintValue && tmpHintValue[0] != '\0') {
            tmpStr = WIN_UTF8ToString(tmpHintValue);
            privacyPolicyLabel = ref new String(tmpStr);
            SDL_free(tmpStr);
        } else {
            privacyPolicyLabel = ref new String(L"Privacy Policy");
        }

        // Register the link, along with a handler to be called if and when it is
        auto cmd = ref new SettingsCommand(L"privacyPolicy", privacyPolicyLabel,
            ref new UICommandInvokedHandler([=](IUICommand ^) {
                Windows::System::Launcher::LaunchUriAsync(ref new Uri(privacyPolicyURL));
        }));
        args->Request->ApplicationCommands->Append(cmd);
    }
}
#endif // if (WINAPI_FAMILY == WINAPI_FAMILY_APP) && (NTDDI_VERSION < NTDDI_WIN10)

void SDL_WinRTApp::OnWindowSizeChanged(CoreWindow^ sender, WindowSizeChangedEventArgs^ args)
{
#if LOG_WINDOW_EVENTS==1
    SDL_Log("%s, size={%f,%f}, bounds={%f,%f,%f,%f}, current orientation=%d, native orientation=%d, auto rot. pref=%d, WINRT_GlobalSDLWindow?=%s\n",
        __FUNCTION__,
        args->Size.Width, args->Size.Height,
        sender->Bounds.X, sender->Bounds.Y, sender->Bounds.Width, sender->Bounds.Height,
        WINRT_DISPLAY_PROPERTY(CurrentOrientation),
        WINRT_DISPLAY_PROPERTY(NativeOrientation),
        WINRT_DISPLAY_PROPERTY(AutoRotationPreferences),
        (WINRT_GlobalSDLWindow ? "yes" : "no"));
#endif

    WINRT_ProcessWindowSizeChange();
}

void SDL_WinRTApp::OnVisibilityChanged(CoreWindow^ sender, VisibilityChangedEventArgs^ args)
{
#if LOG_WINDOW_EVENTS==1
    SDL_Log("%s, visible?=%s, bounds={%f,%f,%f,%f}, WINRT_GlobalSDLWindow?=%s\n",
        __FUNCTION__,
        (args->Visible ? "yes" : "no"),
        sender->Bounds.X, sender->Bounds.Y,
        sender->Bounds.Width, sender->Bounds.Height,
        (WINRT_GlobalSDLWindow ? "yes" : "no"));
#endif

    m_windowVisible = args->Visible;
    if (WINRT_GlobalSDLWindow) {
        SDL_bool wasSDLWindowSurfaceValid = WINRT_GlobalSDLWindow->surface_valid;
        Uint32 latestWindowFlags = WINRT_DetectWindowFlags(WINRT_GlobalSDLWindow);
        if (args->Visible) {
            SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_SHOWN, 0, 0);
            SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
            if (latestWindowFlags & SDL_WINDOW_MAXIMIZED) {
                SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_MAXIMIZED, 0, 0);
            } else {
                SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_RESTORED, 0, 0);
            }
        } else {
            SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_HIDDEN, 0, 0);
            SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
            SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
        }

        // HACK: Prevent SDL's window-hide handling code, which currently
        // triggers a fake window resize (possibly erronously), from
        // marking the SDL window's surface as invalid.
        // A better solution to this probably involves figuring out if the
        // fake window resize can be prevented.
        WINRT_GlobalSDLWindow->surface_valid = wasSDLWindowSurfaceValid;
    }
}

void SDL_WinRTApp::OnWindowActivated(CoreWindow^ sender, WindowActivatedEventArgs^ args)
{
#if LOG_WINDOW_EVENTS==1
    SDL_Log("%s, WINRT_GlobalSDLWindow?=%s\n\n",
        __FUNCTION__,
        (WINRT_GlobalSDLWindow ? "yes" : "no"));
#endif

    /* There's no property in Win 8.x to tell whether a window is active or
       not.  [De]activation events are, however, sent to the app.  We'll just
       record those, in case the CoreWindow gets wrapped by an SDL_Window at
       some future time.
    */
    sender->CustomProperties->Insert("SDLHelperWindowActivationState", args->WindowActivationState);

    SDL_Window * window = WINRT_GlobalSDLWindow;
    if (window) {
        if (args->WindowActivationState != CoreWindowActivationState::Deactivated) {
            SDL_SendWindowEvent(window, SDL_WINDOWEVENT_SHOWN, 0, 0);
            if (SDL_GetKeyboardFocus() != window) {
                SDL_SetKeyboardFocus(window);
            }

            /* Send a mouse-motion event as appropriate.
               This doesn't work when called from OnPointerEntered, at least
               not in WinRT CoreWindow apps (as OnPointerEntered doesn't
               appear to be called after window-reactivation, at least not
               in Windows 10, Build 10586.3 (November 2015 update, non-beta).

               Don't do it on WinPhone 8.0 though, as CoreWindow's 'PointerPosition'
               property isn't available.
             */
#if (WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP) || (NTDDI_VERSION >= NTDDI_WINBLUE)
            Point cursorPos = WINRT_TransformCursorPosition(window, sender->PointerPosition, TransformToSDLWindowSize);
            SDL_SendMouseMotion(window, 0, 0, (int)cursorPos.X, (int)cursorPos.Y);
#endif

            /* TODO, WinRT: see if the Win32 bugfix from http://example.com needs to be applied (on window activation) */

            /* TODO, WinRT: implement clipboard support, if possible */
            // * FIXME: Update keyboard state

            // HACK: Resetting the mouse-cursor here seems to fix
            // WinRT app's mouse cursor may switch to Windows' 'wait' cursor,
            // after a user alt-tabs back into a full-screened SDL app.
            // This bug does not appear to reproduce 100% of the time.
            // It may be a bug in Windows itself (v.10.0.586.36, as tested,
            // and the most-recent as of this writing).
            SDL_SetCursor(NULL);
        } else {
            if (SDL_GetKeyboardFocus() == window) {
                SDL_SetKeyboardFocus(NULL);
            }
        }
    }
}

void SDL_WinRTApp::OnWindowClosed(CoreWindow^ sender, CoreWindowEventArgs^ args)
{
#if LOG_WINDOW_EVENTS==1
    SDL_Log("%s\n", __FUNCTION__);
#endif
    m_windowClosed = true;
}

void SDL_WinRTApp::OnAppActivated(CoreApplicationView^ applicationView, IActivatedEventArgs^ args)
{
    CoreWindow::GetForCurrentThread()->Activate();
}

void SDL_WinRTApp::OnSuspending(Platform::Object^ sender, SuspendingEventArgs^ args)
{
    // Save app state asynchronously after requesting a deferral. Holding a deferral
    // indicates that the application is busy performing suspending operations. Be
    // aware that a deferral may not be held indefinitely. After about five seconds,
    // the app will be forced to exit.

    // ... but first, let the app know it's about to go to the background.
    // The separation of events may be important, given that the deferral
    // runs in a separate thread.  This'll make SDL_APP_WILLENTERBACKGROUND
    // the only event among the two that runs in the main thread.  Given
    // that a few WinRT operations can only be done from the main thread
    // (things that access the WinRT CoreWindow are one example of this),
    SDL_SendAppEvent(SDL_APP_WILLENTERBACKGROUND);

    SuspendingDeferral^ deferral = args->SuspendingOperation->GetDeferral();
    create_task([this, deferral]()
    {
        // Send an app did-enter-background event immediately to observers.
        // CoreDispatcher::ProcessEvents, which is the backbone on which
        // SDL_WinRTApp::PumpEvents is built, will not return to its caller
        // once it sends out a suspend event.  Any events posted to SDL's
        // event queue won't get received until the WinRT app is resumed.
        // SDL_AddEventWatch() may be used to receive app-suspend events on
        SDL_SendAppEvent(SDL_APP_DIDENTERBACKGROUND);

        // Let the Direct3D 11 renderer prepare for the app to be backgrounded.
        // This is necessary for Windows 8.1, possibly elsewhere in the future.
        // More details at: http://example.com
#if SDL_VIDEO_RENDER_D3D11 && !SDL_RENDER_DISABLED
        if (WINRT_GlobalSDLWindow) {
            SDL_Renderer * renderer = SDL_GetRenderer(WINRT_GlobalSDLWindow);
            if (renderer && (SDL_strcmp(renderer->info.name, "direct3d11") == 0)) {
                D3D11_Trim(renderer);
            }
        }
#endif

        deferral->Complete();
    });
}

void SDL_WinRTApp::OnResuming(Platform::Object^ sender, Platform::Object^ args)
{
    // Restore any data or state that was unloaded on suspend. By default, data
    // and state are persisted when resuming from suspend. Note that these events
    // do not occur if the app was previously terminated.
    SDL_SendAppEvent(SDL_APP_WILLENTERFOREGROUND);
    SDL_SendAppEvent(SDL_APP_DIDENTERFOREGROUND);
}

void SDL_WinRTApp::OnExiting(Platform::Object^ sender, Platform::Object^ args)
{
    SDL_SendAppEvent(SDL_APP_TERMINATING);
}

static void
WINRT_LogPointerEvent(const char * header, Windows::UI::Core::PointerEventArgs ^ args, Windows::Foundation::Point transformedPoint)
{
    Windows::UI::Input::PointerPoint ^ pt = args->CurrentPoint;
    SDL_Log("%s: Position={%f,%f}, Transformed Pos={%f, %f}, MouseWheelDelta=%d, FrameId=%d, PointerId=%d, SDL button=%d\n",
        header,
        pt->Position.X, pt->Position.Y,
        transformedPoint.X, transformedPoint.Y,
        pt->Properties->MouseWheelDelta,
        pt->FrameId,
        pt->PointerId,
        WINRT_GetSDLButtonForPointerPoint(pt));
}

void SDL_WinRTApp::OnPointerPressed(CoreWindow^ sender, PointerEventArgs^ args)
{
#if LOG_POINTER_EVENTS
    WINRT_LogPointerEvent("pointer pressed", args, WINRT_TransformCursorPosition(WINRT_GlobalSDLWindow, args->CurrentPoint->Position, TransformToSDLWindowSize));
#endif

    WINRT_ProcessPointerPressedEvent(WINRT_GlobalSDLWindow, args->CurrentPoint);
}

void SDL_WinRTApp::OnPointerMoved(CoreWindow^ sender, PointerEventArgs^ args)
{
#if LOG_POINTER_EVENTS
    WINRT_LogPointerEvent("pointer moved", args, WINRT_TransformCursorPosition(WINRT_GlobalSDLWindow, args->CurrentPoint->Position, TransformToSDLWindowSize));
#endif

    WINRT_ProcessPointerMovedEvent(WINRT_GlobalSDLWindow, args->CurrentPoint);
}

void SDL_WinRTApp::OnPointerReleased(CoreWindow^ sender, PointerEventArgs^ args)
{
#if LOG_POINTER_EVENTS
    WINRT_LogPointerEvent("pointer released", args, WINRT_TransformCursorPosition(WINRT_GlobalSDLWindow, args->CurrentPoint->Position, TransformToSDLWindowSize));
#endif

    WINRT_ProcessPointerReleasedEvent(WINRT_GlobalSDLWindow, args->CurrentPoint);
}

void SDL_WinRTApp::OnPointerEntered(CoreWindow^ sender, PointerEventArgs^ args)
{
#if LOG_POINTER_EVENTS
    WINRT_LogPointerEvent("pointer entered", args, WINRT_TransformCursorPosition(WINRT_GlobalSDLWindow, args->CurrentPoint->Position, TransformToSDLWindowSize));
#endif

    WINRT_ProcessPointerEnteredEvent(WINRT_GlobalSDLWindow, args->CurrentPoint);
}

void SDL_WinRTApp::OnPointerExited(CoreWindow^ sender, PointerEventArgs^ args)
{
#if LOG_POINTER_EVENTS
    WINRT_LogPointerEvent("pointer exited", args, WINRT_TransformCursorPosition(WINRT_GlobalSDLWindow, args->CurrentPoint->Position, TransformToSDLWindowSize));
#endif

    WINRT_ProcessPointerExitedEvent(WINRT_GlobalSDLWindow, args->CurrentPoint);
}

void SDL_WinRTApp::OnPointerWheelChanged(CoreWindow^ sender, PointerEventArgs^ args)
{
#if LOG_POINTER_EVENTS
    WINRT_LogPointerEvent("pointer wheel changed", args, WINRT_TransformCursorPosition(WINRT_GlobalSDLWindow, args->CurrentPoint->Position, TransformToSDLWindowSize));
#endif

    WINRT_ProcessPointerWheelChangedEvent(WINRT_GlobalSDLWindow, args->CurrentPoint);
}

void SDL_WinRTApp::OnMouseMoved(MouseDevice^ mouseDevice, MouseEventArgs^ args)
{
    WINRT_ProcessMouseMovedEvent(WINRT_GlobalSDLWindow, args);
}

void SDL_WinRTApp::OnKeyDown(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::KeyEventArgs^ args)
{
    WINRT_ProcessKeyDownEvent(args);
}

void SDL_WinRTApp::OnKeyUp(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::KeyEventArgs^ args)
{
    WINRT_ProcessKeyUpEvent(args);
}

void SDL_WinRTApp::OnCharacterReceived(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::CharacterReceivedEventArgs^ args)
{
    WINRT_ProcessCharacterReceivedEvent(args);
}

template <typename BackButtonEventArgs>
static void WINRT_OnBackButtonPressed(BackButtonEventArgs ^ args)
{
    SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_AC_BACK);
    SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_AC_BACK);

    if (SDL_GetHintBoolean(SDL_HINT_WINRT_HANDLE_BACK_BUTTON, SDL_FALSE)) {
        args->Handled = true;
    }
}

#if NTDDI_VERSION >= NTDDI_WIN10
void SDL_WinRTApp::OnBackButtonPressed(Platform::Object^ sender, Windows::UI::Core::BackRequestedEventArgs^ args)

{
    WINRT_OnBackButtonPressed(args);
}
#elif WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
void SDL_WinRTApp::OnBackButtonPressed(Platform::Object^ sender, Windows::Phone::UI::Input::BackPressedEventArgs^ args)

{
    WINRT_OnBackButtonPressed(args);
}
#endif

#if NTDDI_VERSION >= NTDDI_WIN10
void SDL_WinRTApp::OnGamepadAdded(Platform::Object ^sender, Windows::Gaming::Input::Gamepad ^gamepad)
{
    /* HACK ALERT: Nothing needs to be done here, as this method currently
       only exists to allow something to be registered with Win10's
       GamepadAdded event, an operation that seems to be necessary to get
       Xinput-based detection to work on Xbox One.
    */
}
#endif

/* vi: set ts=4 sw=4 expandtab: */


bool BlockAssembler::SkipMapTxEntry(CTxMemPool::txiter it, indexed_modified_transaction_set &mapModifiedTx, CTxMemPool::setEntries &failedTx)
{
    assert (it != mempool.mapTx.end());
    return mapModifiedTx.count(it) || inBlock.count(it) || failedTx.count(it);
}

#include "miner.h"

#include "amount.h"
#include "chain.h"
#include "chainparams.h"
#include "coins.h"
#include "consensus/consensus.h"
#include "consensus/tx_verify.h"
#include "consensus/merkle.h"
#include "consensus/validation.h"
#include "hash.h"
#include "validation.h"
#include "net.h"
#include "policy/feerate.h"
#include "policy/policy.h"
#include "pow.h"
#include "primitives/transaction.h"
#include "script/standard.h"
#include "timedata.h"
#include "txmempool.h"
#include "util.h"
#include "utilmoneystr.h"
#include "validationinterface.h"

#include <algorithm>
#include <queue>
#include <utility>

#include "contract/config.h"
#include "contract/ethstate.h"
#include "contract/ethtransaction.h"
#include "contract/ethtxconverter.h"
#include "contract/contractexecutor.h"

// BitcoinMiner

// Unconfirmed transactions in the memory pool often depend on other
// transactions in the memory pool. When we select transactions from the
// pool, we select by highest fee rate of a transaction combined with all
// its ancestors.

uint64_t nLastBlockTx = 0;
uint64_t nLastBlockWeight = 0;

int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev)
{
    int64_t nOldTime = pblock->nTime;
    int64_t nNewTime = std::max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime());

    if (nOldTime < nNewTime)
        pblock->nTime = nNewTime;

    // Updating time can change work required on testnet:
    if (consensusParams.fPowAllowMinDifficultyBlocks)
        pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, consensusParams);

    return nNewTime - nOldTime;
}

BlockAssembler::Options::Options() {
    blockMinFeeRate = CFeeRate(DEFAULT_BLOCK_MIN_TX_FEE);
    nBlockMaxWeight = DEFAULT_BLOCK_MAX_WEIGHT;
}

BlockAssembler::BlockAssembler(const CChainParams& params, const Options& options) : chainparams(params)
{
    blockMinFeeRate = options.blockMinFeeRate;
    // Limit weight to between 4K and MAX_BLOCK_WEIGHT-4K for sanity:
    nBlockMaxWeight = std::max<size_t>(4000, std::min<size_t>(MAX_BLOCK_WEIGHT - 4000, options.nBlockMaxWeight));
}

static BlockAssembler::Options DefaultOptions(const CChainParams& params)
{
    // Block resource limits
    // If neither -blockmaxsize or -blockmaxweight is given, limit to DEFAULT_BLOCK_MAX_*
    // If only one is given, only restrict the specified resource.
    // If both are given, restrict both.
    BlockAssembler::Options options;
    options.nBlockMaxWeight = gArgs.GetArg("-blockmaxweight", DEFAULT_BLOCK_MAX_WEIGHT);
    if (gArgs.IsArgSet("-blockmintxfee")) {
        CAmount n = 0;
        ParseMoney(gArgs.GetArg("-blockmintxfee", ""), n);
        options.blockMinFeeRate = CFeeRate(n);
    } else {
        options.blockMinFeeRate = CFeeRate(DEFAULT_BLOCK_MIN_TX_FEE);
    }
    return options;
}

BlockAssembler::BlockAssembler(const CChainParams& params) : BlockAssembler(params, DefaultOptions(params)) {}

void BlockAssembler::resetBlock()
{
    inBlock.clear();

    // Reserve space for coinbase tx
    nBlockWeight = 4000;
    nBlockSigOpsCost = 400;
    fIncludeWitness = false;

    // These counters do not include coinbase tx
    nBlockTx = 0;
    nFees = 0;
}

std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn, bool fMineWitnessTx)
{
    int64_t nTimeStart = GetTimeMicros();

    resetBlock();

    pblocktemplate.reset(new CBlockTemplate());

    if(!pblocktemplate.get())
        return nullptr;
    pblock = &pblocktemplate->block; // pointer for convenience

    // Add dummy coinbase tx as first transaction
    pblock->vtx.emplace_back();
    pblocktemplate->vTxFees.push_back(-1); // updated at end
    pblocktemplate->vTxSigOpsCost.push_back(-1); // updated at end

    LOCK2(cs_main, mempool.cs);
    CBlockIndex* pindexPrev = chainActive.Tip();
    nHeight = pindexPrev->nHeight + 1;

    pblock->nVersion = ComputeBlockVersion(pindexPrev, chainparams.GetConsensus());
    // -regtest only: allow overriding block.nVersion with
    // -blockversion=N to test forking scenarios
    if (chainparams.MineBlocksOnDemand())
        pblock->nVersion = gArgs.GetArg("-blockversion", pblock->nVersion);

    pblock->nTime = GetAdjustedTime();
    const int64_t nMedianTimePast = pindexPrev->GetMedianTimePast();

    nLockTimeCutoff = (STANDARD_LOCKTIME_VERIFY_FLAGS & LOCKTIME_MEDIAN_TIME_PAST)
                       ? nMedianTimePast
                       : pblock->GetBlockTime();

    // Decide whether to include witness transactions
    // This is only needed in case the witness softfork activation is reverted
    // (which would require a very deep reorganization) or when
    // -promiscuousmempoolflags is used.
    // TODO: replace this with a call to main to assess validity of a mempool
    // transaction (which in most cases can be a no-op).
    fIncludeWitness = IsWitnessEnabled(pindexPrev, chainparams.GetConsensus()) && fMineWitnessTx;

    int64_t nTime1 = GetTimeMicros();

    nLastBlockTx = nBlockTx;
    nLastBlockWeight = nBlockWeight;

    // Create coinbase transaction.
    CMutableTransaction coinbaseTx;
    coinbaseTx.vin.resize(1);
    coinbaseTx.vin[0].prevout.SetNull();
    coinbaseTx.vout.resize(1);
    coinbaseTx.vout[0].scriptPubKey = scriptPubKeyIn;
    coinbaseTx.vout[0].nValue = nFees + GetBlockSubsidy(nHeight, chainparams.GetConsensus());
    coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0;
    originalRewardTx = coinbaseTx;
    pblock->vtx[0] = MakeTransactionRef(std::move(coinbaseTx));

    EthState::Instance()->SetEngineSchedule();
    minGasPrice = MIN_GAS_PRICE;
    if(gArgs.IsArgSet("-staker-min-tx-gas-price")) {
        CAmount stakerMinGasPrice;
        if(ParseMoney(gArgs.GetArg("-staker-min-tx-gas-price", ""), stakerMinGasPrice)) {
            minGasPrice = std::max(minGasPrice, (uint64_t)stakerMinGasPrice);
        }
    }
    hardBlockGasLimit = DEFAULT_BLOCK_GAS_LIMIT;
    softBlockGasLimit = gArgs.GetArg("-staker-soft-block-gas-limit", hardBlockGasLimit);
    softBlockGasLimit = std::min(softBlockGasLimit, hardBlockGasLimit);
    txGasLimit = gArgs.GetArg("-staker-max-tx-gas-limit", softBlockGasLimit);

    dev::h256 oldHashStateRoot(EthState::Instance()->rootHash());
    dev::h256 oldHashUTXORoot(EthState::Instance()->rootHashUTXO());

    int nPackagesSelected = 0;
    int nDescendantsUpdated = 0;
    addPackageTxs(minGasPrice, nPackagesSelected, nDescendantsUpdated);

    EthState::Instance()->setRoot(oldHashStateRoot);
    EthState::Instance()->setUTXORoot(oldHashUTXORoot);

    // This should already be populated by AddBlock in case of contracts, but if no contracts
    // Then it won't get populated
    RebuildRefundTransaction();

    pblocktemplate->vchCoinbaseCommitment = GenerateCoinbaseCommitment(*pblock, pindexPrev, chainparams.GetConsensus());
    pblocktemplate->vTxFees[0] = -nFees;

    // Fill in header
    pblock->hashPrevBlock  = pindexPrev->GetBlockHash();
    UpdateTime(pblock, chainparams.GetConsensus(), pindexPrev);
    pblock->nBits          = GetNextWorkRequired(pindexPrev, pblock, chainparams.GetConsensus());
    pblock->nNonce         = 0;
    pblocktemplate->vTxSigOpsCost[0] = WITNESS_SCALE_FACTOR * GetLegacySigOpCount(*pblock->vtx[0]);

    LogPrintf("CreateNewBlock(): block weight: %u txs: %u fees: %ld sigops %d nBits:%x nBitsStr:%s\n", GetBlockWeight(*pblock), nBlockTx, nFees, nBlockSigOpsCost,
              pblock->nBits, ArithToUint256(arith_uint256().SetCompact(pblock->nBits)).GetHex());

    CValidationState state;
    if (!TestBlockValidity(state, chainparams, *pblock, pindexPrev, false, false)) {
        throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, FormatStateMessage(state)));
    }
    int64_t nTime2 = GetTimeMicros();

    LogPrint(BCLog::BENCH, "CreateNewBlock() packages: %.2fms (%d packages, %d updated descendants), validity: %.2fms (total %.2fms)\n", 0.001 * (nTime1 - nTimeStart), nPackagesSelected, nDescendantsUpdated, 0.001 * (nTime2 - nTime1), 0.001 * (nTime2 - nTimeStart));

    return std::move(pblocktemplate);
}

void BlockAssembler::onlyUnconfirmed(CTxMemPool::setEntries& testSet)
{
    for (CTxMemPool::setEntries::iterator iit = testSet.begin(); iit != testSet.end(); ) {
        // Only test txs not already in the block
        if (inBlock.count(*iit)) {
            testSet.erase(iit++);
        }
        else {
            iit++;
        }
    }
}

bool BlockAssembler::TestPackage(uint64_t packageSize, int64_t packageSigOpsCost)
{
    // TODO: switch to weight-based accounting for packages instead of vsize-based accounting.
    if (nBlockWeight + WITNESS_SCALE_FACTOR * packageSize >= nBlockMaxWeight)
        return false;
    if (nBlockSigOpsCost + packageSigOpsCost >= MAX_BLOCK_SIGOPS_COST)
        return false;
    return true;
}

// Perform transaction-level checks before adding to block:
// - premature witness (in case segwit transactions are added to mempool before
//   segwit activation)
bool BlockAssembler::TestPackageTransactions(const CTxMemPool::setEntries& package)
{
    for (const CTxMemPool::txiter it : package) {
        if (!IsFinalTx(it->GetTx(), nHeight, nLockTimeCutoff))
            return false;
        if (!fIncludeWitness && it->GetTx().HasWitness())
            return false;
    }
    return true;
}

void BlockAssembler::AddToBlock(CTxMemPool::txiter iter)
{
    pblock->vtx.emplace_back(iter->GetSharedTx());
    pblocktemplate->vTxFees.push_back(iter->GetFee());
    pblocktemplate->vTxSigOpsCost.push_back(iter->GetSigOpCost());
    nBlockWeight += iter->GetTxWeight();
    ++nBlockTx;
    nBlockSigOpsCost += iter->GetSigOpCost();
    nFees += iter->GetFee();
    inBlock.insert(iter);

    bool fPrintPriority = gArgs.GetBoolArg("-printpriority", DEFAULT_PRINTPRIORITY);
    if (fPrintPriority) {
        LogPrintf("fee %s txid %s\n",
                  CFeeRate(iter->GetModifiedFee(), iter->GetTxSize()).ToString(),
                  iter->GetTx().GetHash().ToString());
    }
}

bool BlockAssembler::TryToAddContractToBlock(CTxMemPool::txiter iter, uint64_t minGasPrice)
{
    // Check contract active.
    if (!IsContractEnabled(chainActive.Tip(), chainparams.GetConsensus())) {
        return false;
    }

    dev::h256 oldHashStateRoot(EthState::Instance()->rootHash());
    dev::h256 oldHashUTXORoot(EthState::Instance()->rootHashUTXO());
    // Operate on local vars first, then later apply to `this`
    uint64_t blockWeight = nBlockWeight;
    uint64_t blockSigOpsCost = nBlockSigOpsCost;

    std::vector<EthTransaction> ethTxs;
    EthTxConverter converter(iter->GetTx(), NULL, &pblock->vtx);
    if (!converter.Convert(ethTxs)) {
        // This check already happens when accepting txs into mempool
        // Therefore, this can only be triggered by using raw transactions on the staker itself
        return false;
    }
    dev::u256 txGas = 0;
    for (const EthTransaction &ethTx : ethTxs) {
        txGas += ethTx.gas();
        if (txGas > txGasLimit) {
            // Limit the tx gas limit by the soft limit if such a limit has been specified.
            return false;
        }

        if (contractExeResult.totalGasUsed + ethTx.gas() > softBlockGasLimit) {
            // If this transaction's gasLimit could cause block gas limit to be exceeded, then don't add it
            return false;
        }
        if (ethTx.gasPrice() < minGasPrice) {
            // If this transaction's gasPrice is less than the current minGasPrice don't add it
            return false;
        }
    }
    // We need to pass the block gas limit (not the soft limit) since it is consensus critical.
    ContractExecutor executor(*pblock, ethTxs, hardBlockGasLimit);
    if (!executor.Execut()) {
        // Error, don't add contract
        EthState::Instance()->setRoot(oldHashStateRoot);
        EthState::Instance()->setUTXORoot(oldHashUTXORoot);
        return false;
    }

    ExecutionResult testcontractExeResult;
    if (!executor.GetResult(testcontractExeResult)) {
        EthState::Instance()->setRoot(oldHashStateRoot);
        EthState::Instance()->setUTXORoot(oldHashUTXORoot);
        return false;
    }

    if (contractExeResult.totalGasUsed + testcontractExeResult.totalGasUsed > softBlockGasLimit) {
        // If this transaction could cause block gas limit to be exceeded, then don't add it
        EthState::Instance()->setRoot(oldHashStateRoot);
        EthState::Instance()->setUTXORoot(oldHashUTXORoot);
        return false;
    }

    // Apply contractTx costs to local state
    blockWeight += iter->GetTxWeight();
    blockSigOpsCost += iter->GetSigOpCost();
    // Apply value-transfer txs to local state
    for (const CTransaction& tx : testcontractExeResult.transferTxs) {
        blockWeight += GetTransactionWeight(tx);
        blockSigOpsCost += GetLegacySigOpCount(tx);
    }

    // Calculate sigops from new refund/proof tx

    // First, subtract old proof tx
    blockSigOpsCost -= GetLegacySigOpCount(*pblock->vtx[0]);

    // Manually rebuild refundtx
    CMutableTransaction contrTx(*pblock->vtx[0]);
    // Note, this will need changed for MPoS
    int i = contrTx.vout.size();
    contrTx.vout.resize(contrTx.vout.size() + testcontractExeResult.refundTxOuts.size());
    for (CTxOut& vout : testcontractExeResult.refundTxOuts) {
        contrTx.vout[i] = vout;
        i++;
    }
    blockSigOpsCost += GetLegacySigOpCount(contrTx);
    // All contract costs now applied to local state

    // Check if block will be too big or too expensive with this contract execution
    if (blockSigOpsCost * WITNESS_SCALE_FACTOR > MAX_BLOCK_SIGOPS_COST) {
        // Contract will not be added to block, so revert state to before we tried
        EthState::Instance()->setRoot(oldHashStateRoot);
        EthState::Instance()->setUTXORoot(oldHashUTXORoot);
        return false;
    }

    // Block is not too big, so apply the contract execution and it's results to the actual block

    // Apply local bytecode to global bytecode state
    contractExeResult.totalGasUsed += testcontractExeResult.totalGasUsed;
    contractExeResult.totalRefund += testcontractExeResult.totalRefund;
    contractExeResult.refundTxOuts.insert(contractExeResult.refundTxOuts.end(), testcontractExeResult.refundTxOuts.begin(), testcontractExeResult.refundTxOuts.end());
    contractExeResult.transferTxs = std::move(testcontractExeResult.transferTxs);

    pblock->vtx.emplace_back(iter->GetSharedTx());
    pblocktemplate->vTxFees.push_back(iter->GetFee());
    pblocktemplate->vTxSigOpsCost.push_back(iter->GetSigOpCost());
    nBlockWeight += iter->GetTxWeight();
    ++nBlockTx;
    nBlockSigOpsCost += iter->GetSigOpCost();
    nFees += iter->GetFee();
    inBlock.insert(iter);

    for (const CTransaction& tx : contractExeResult.transferTxs) {
        pblock->vtx.emplace_back(MakeTransactionRef(std::move(tx)));
        nBlockWeight += GetTransactionWeight(tx);
        nBlockSigOpsCost += GetLegacySigOpCount(tx);
        ++nBlockTx;
    }
    // Calculate sigops from new refund/proof tx
    nBlockSigOpsCost -= GetLegacySigOpCount(*pblock->vtx[0]);
    RebuildRefundTransaction();
    nBlockSigOpsCost += GetLegacySigOpCount(*pblock->vtx[0]);

    contractExeResult.transferTxs.clear();

    return true;
}

void BlockAssembler::RebuildRefundTransaction()
{
    CMutableTransaction contrTx(originalRewardTx);
    contrTx.vout[0].nValue = nFees + GetBlockSubsidy(nHeight, chainparams.GetConsensus());
    contrTx.vout[0].nValue -= contractExeResult.totalRefund;
    int i = contrTx.vout.size();
    contrTx.vout.resize(contrTx.vout.size() + contractExeResult.refundTxOuts.size());
    for (CTxOut& vout : contractExeResult.refundTxOuts) {
        contrTx.vout[i] = vout;
        i++;
    }
    pblock->vtx[0] = MakeTransactionRef(std::move(contrTx));
}

int BlockAssembler::UpdatePackagesForAdded(const CTxMemPool::setEntries& alreadyAdded,
        indexed_modified_transaction_set &mapModifiedTx)
{
    int nDescendantsUpdated = 0;
    for (const CTxMemPool::txiter it : alreadyAdded) {
        CTxMemPool::setEntries descendants;
        mempool.CalculateDescendants(it, descendants);
        // Insert all descendants (not yet in block) into the modified set
        for (CTxMemPool::txiter desc : descendants) {
            if (alreadyAdded.count(desc))
                continue;
            ++nDescendantsUpdated;
            modtxiter mit = mapModifiedTx.find(desc);
            if (mit == mapModifiedTx.end()) {
                CTxMemPoolModifiedEntry modEntry(desc);
                modEntry.nSizeWithAncestors -= it->GetTxSize();
                modEntry.nModFeesWithAncestors -= it->GetModifiedFee();
                modEntry.nSigOpCostWithAncestors -= it->GetSigOpCost();
                mapModifiedTx.insert(modEntry);
            } else {
                mapModifiedTx.modify(mit, update_for_parent_inclusion(it));
            }
        }
    }
    return nDescendantsUpdated;
}

// Skip entries in mapTx that are already in a block or are present
// in mapModifiedTx (which implies that the mapTx ancestor state is
// stale due to ancestor inclusion in the block)
// Also skip transactions that we've already failed to add. This can happen if
// we consider a transaction in mapModifiedTx and it fails: we can then
// potentially consider it again while walking mapTx.  It's currently
// guaranteed to fail again, but as a belt-and-suspenders check we put it in
// failedTx and avoid re-evaluation, since the re-evaluation would be using
// cached size/sigops/fee values that are not actually correct.

void BlockAssembler::SortForBlock(const CTxMemPool::setEntries& package, CTxMemPool::txiter entry, std::vector<CTxMemPool::txiter>& sortedEntries)
{
    // Sort package by ancestor count
    // If a transaction A depends on transaction B, then A's ancestor count
    // must be greater than B's.  So this is sufficient to validly order the
    // transactions for block inclusion.
    sortedEntries.clear();
    sortedEntries.insert(sortedEntries.begin(), package.begin(), package.end());
    std::sort(sortedEntries.begin(), sortedEntries.end(), CompareTxIterByAncestorCount());
}

// This transaction selection algorithm orders the mempool based
// on feerate of a transaction including all unconfirmed ancestors.
// Since we don't remove transactions from the mempool as we select them
// for block inclusion, we need an alternate method of updating the feerate
// of a transaction with its not-yet-selected ancestors as we go.
// This is accomplished by walking the in-mempool descendants of selected
// transactions and storing a temporary modified state in mapModifiedTxs.
// Each time through the loop, we compare the best transaction in
// mapModifiedTxs with the next transaction in the mempool to decide what
// transaction package to work on next.
void BlockAssembler::addPackageTxs(uint64_t minGasPrice, int &nPackagesSelected, int &nDescendantsUpdated)
{
    // mapModifiedTx will store sorted packages after they are modified
    // because some of their txs are already in the block
    indexed_modified_transaction_set mapModifiedTx;
    // Keep track of entries that failed inclusion, to avoid duplicate work
    CTxMemPool::setEntries failedTx;

    // Start by adding all descendants of previously added txs to mapModifiedTx
    // and modifying them for their already included ancestors
    UpdatePackagesForAdded(inBlock, mapModifiedTx);

    CTxMemPool::indexed_transaction_set::index<ancestor_score_or_gas_price>::type::iterator mi = mempool.mapTx.get<ancestor_score_or_gas_price>().begin();
    CTxMemPool::txiter iter;

    // Limit the number of attempts to add transactions to the block when it is
    // close to full; this is just a simple heuristic to finish quickly if the
    // mempool has a lot of entries.
    const int64_t MAX_CONSECUTIVE_FAILURES = 1000;
    int64_t nConsecutiveFailed = 0;

    while (mi != mempool.mapTx.get<ancestor_score_or_gas_price>().end() || !mapModifiedTx.empty())
    {
        // First try to find a new transaction in mapTx to evaluate.
        if (mi != mempool.mapTx.get<ancestor_score_or_gas_price>().end() &&
                SkipMapTxEntry(mempool.mapTx.project<0>(mi), mapModifiedTx, failedTx)) {
            ++mi;
            continue;
        }

        // Now that mi is not stale, determine which transaction to evaluate:
        // the next entry from mapTx, or the best from mapModifiedTx?
        bool fUsingModified = false;

        modtxscoreiter modit = mapModifiedTx.get<ancestor_score_or_gas_price>().begin();
        if (mi == mempool.mapTx.get<ancestor_score_or_gas_price>().end()) {
            // We're out of entries in mapTx; use the entry from mapModifiedTx
            iter = modit->iter;
            fUsingModified = true;
        } else {
            // Try to compare the mapTx entry to the mapModifiedTx entry
            iter = mempool.mapTx.project<0>(mi);
            if (modit != mapModifiedTx.get<ancestor_score_or_gas_price>().end() &&
                    CompareModifiedEntry()(*modit, CTxMemPoolModifiedEntry(iter))) {
                // The best entry in mapModifiedTx has higher score
                // than the one from mapTx.
                // Switch which transaction (package) to consider
                iter = modit->iter;
                fUsingModified = true;
            } else {
                // Either no entry in mapModifiedTx, or it's worse than mapTx.
                // Increment mi for the next loop iteration.
                ++mi;
            }
        }

        // We skip mapTx entries that are inBlock, and mapModifiedTx shouldn't
        // contain anything that is inBlock.
        assert(!inBlock.count(iter));

        uint64_t packageSize = iter->GetSizeWithAncestors();
        CAmount packageFees = iter->GetModFeesWithAncestors();
        int64_t packageSigOpsCost = iter->GetSigOpCostWithAncestors();
        if (fUsingModified) {
            packageSize = modit->nSizeWithAncestors;
            packageFees = modit->nModFeesWithAncestors;
            packageSigOpsCost = modit->nSigOpCostWithAncestors;
        }

        if (packageFees < blockMinFeeRate.GetFee(packageSize)) {
            // Everything else we might consider has a lower fee rate
            return;
        }

        if (!TestPackage(packageSize, packageSigOpsCost)) {
            if (fUsingModified) {
                // Since we always look at the best entry in mapModifiedTx,
                // we must erase failed entries so that we can consider the
                // next best entry on the next loop iteration
                mapModifiedTx.get<ancestor_score_or_gas_price>().erase(modit);
                failedTx.insert(iter);
            }

            ++nConsecutiveFailed;

            if (nConsecutiveFailed > MAX_CONSECUTIVE_FAILURES && nBlockWeight >
                    nBlockMaxWeight - 4000) {
                // Give up if we're close to full and haven't succeeded in a while
                break;
            }
            continue;
        }

        CTxMemPool::setEntries ancestors;
        uint64_t nNoLimit = std::numeric_limits<uint64_t>::max();
        std::string dummy;
        mempool.CalculateMemPoolAncestors(*iter, ancestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy, false);

        onlyUnconfirmed(ancestors);
        ancestors.insert(iter);

        // Test if all tx's are Final
        if (!TestPackageTransactions(ancestors)) {
            if (fUsingModified) {
                mapModifiedTx.get<ancestor_score_or_gas_price>().erase(modit);
                failedTx.insert(iter);
            }
            continue;
        }

        // This transaction will make it in; reset the failed counter.
        nConsecutiveFailed = 0;

        // Package can be added. Sort the entries in a valid order.
        std::vector<CTxMemPool::txiter> sortedEntries;
        SortForBlock(ancestors, iter, sortedEntries);

        bool wasAdded = true;
        for (size_t i = 0; i < sortedEntries.size(); ++i) {
            if (!wasAdded) {
                // If earlier ancestor failed, then skip the rest of the transactions
                mapModifiedTx.erase(sortedEntries[i]);
                wasAdded = false;
                continue;
            }
            const CTransaction& tx = sortedEntries[i]->GetTx();
            if (wasAdded) {
                if (tx.HasCreateOrSendOp()) {
                    wasAdded = TryToAddContractToBlock(sortedEntries[i], minGasPrice);
                    if (!wasAdded) {
                        if (fUsingModified) {
                            // This only needs to be done once to mark the whole package (everything in sortedEntries) as failed
                            mapModifiedTx.get<ancestor_score_or_gas_price>().erase(modit);
                            failedTx.insert(iter);
                        }
                    }
                } else {
                    AddToBlock(sortedEntries[i]);
                }
            }
            // Erase from the modified set, if present
            mapModifiedTx.erase(sortedEntries[i]);
        }

        if(!wasAdded){
            // Skip UpdatePackages if a transaction failed to be added (match TestPackage logic)
            continue;
        }

        ++nPackagesSelected;

        // Update transactions that depend on each of these
        nDescendantsUpdated += UpdatePackagesForAdded(ancestors, mapModifiedTx);
    }
}

void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned int& nExtraNonce)
{
    // Update nExtraNonce
    static uint256 hashPrevBlock;
    if (hashPrevBlock != pblock->hashPrevBlock)
    {
        nExtraNonce = 0;
        hashPrevBlock = pblock->hashPrevBlock;
    }
    ++nExtraNonce;
    unsigned int nHeight = pindexPrev->nHeight+1; // Height first in coinbase required for block.version=2
    CMutableTransaction txCoinbase(*pblock->vtx[0]);
    txCoinbase.vin[0].scriptSig = (CScript() << nHeight << CScriptNum(nExtraNonce)) + COINBASE_FLAGS;
    assert(txCoinbase.vin[0].scriptSig.size() <= 100);

    pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase));
    pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
}


static void patch_field(char* buf, int beg, int end)
{
   while((beg <= end) && (buf[end] == BLANK))
      end--;

   while((beg <= end) && (buf[beg] == BLANK))
      beg++;

   for(int i = beg; i <= end; i++)
      if (buf[i] == BLANK)
         buf[i] = PATCH_CHAR;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*                                                                           */
/*                  This file is part of the class library                   */
/*       SoPlex --- the Sequential object-oriented simPlex.                  */
/*                                                                           */
/*                                                                           */
/*                                                                           */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/**@file  mpsinput.cpp
 * @brief Read MPS format files.
 */

#include <assert.h>
#include <ctype.h>
#include <string.h>

#include "spxdefines.h"
#include "mpsinput.h"
#include "spxout.h"

#define PATCH_CHAR    '_'
#define BLANK         ' '

namespace soplex
{

/// fill the line from \p pos up to column 80 with blanks.
static void clear_from(char* buf, int pos)
{
   for(int i = pos; i < 80; i++)
      buf[i] = BLANK;
   buf[80] = '\0';
}

/// change all blanks inside a field to #PATCH_CHAR.

/// read a MPS format data line and parse the fields.
bool MPSInput::readLine()
{
   int   len;
   int   space;
   char* s;
   bool  is_marker;
   bool  is_comment;

   do
   {
      m_f0 = m_f1 = m_f2 = m_f3 = m_f4 = m_f5 = 0;
      is_marker = false;

      // Read until we have a non-empty, non-comment line.
      do
      {
         if( !m_input.getline(m_buf, sizeof(m_buf)).good() && !m_input.eof() )
            return false;
        m_lineno++;

        MSG_DEBUG( std::cout << "DMPSIN01 Line " << m_lineno
                          << " " << m_buf << std::endl; )

        /* check if comment line */
        is_comment = true;
        if( m_buf[0] == '*' )
           continue;

        /* normalize line and check if it is empty */
        len = int(strlen(m_buf));
        for( int i = 0; i < len; i++ )
        {
           if( m_buf[i] == '\t' || m_buf[i] == '\n' || m_buf[i] == '\r' )
              m_buf[i] = BLANK;
           else if( m_buf[i] != BLANK )
              is_comment = false;
        }
      }
      while( is_comment );

      len = int(strlen(m_buf));
      if (len < 80)
         clear_from(m_buf, len);

      assert(strlen(m_buf) >= 80);

      /* Look for new section
       */
      if (*m_buf != BLANK)
      {
         m_f0 = strtok(&m_buf[0], " ");

         assert(m_f0 != 0);

         m_f1 = strtok(0, " ");

         return true;
      }

      if (!m_is_new_format)
      {
         /* Test for fixed format comments
          */
         if ((m_buf[14] == '$') && (m_buf[13] == ' '))
            clear_from(m_buf, 14);
         else if ((m_buf[39] == '$') && (m_buf[38] == ' '))
            clear_from(m_buf, 39);

         /* Test for fixed format
          */
         space = m_buf[12] | m_buf[13]
            | m_buf[22] | m_buf[23]
            | m_buf[36] | m_buf[37] | m_buf[38]
            | m_buf[47] | m_buf[48]
            | m_buf[61] | m_buf[62] | m_buf[63];

         if (space == BLANK || len < 13)
         {
            /* Now we have space at the right positions.
             * But are there also the non space where they
             * should be ?
             */
            bool number = isdigit(m_buf[24]) || isdigit(m_buf[25])
               || isdigit(m_buf[26]) || isdigit(m_buf[27])
               || isdigit(m_buf[28]) || isdigit(m_buf[29])
               || isdigit(m_buf[30]) || isdigit(m_buf[31])
               || isdigit(m_buf[32]) || isdigit(m_buf[33])
               || isdigit(m_buf[34]) || isdigit(m_buf[35]);

            /* len < 13 is handle ROW lines with embedded spaces
             * in the names correctly
             */
            if (number || len < 13)
            {
               /* Now we assume fixed format, so we patch possible embedded spaces.
                */
               patch_field(m_buf,  4, 12);
               patch_field(m_buf, 14, 22);
               patch_field(m_buf, 39, 47);
            }
            else
            {
               if (  m_section == COLUMNS || m_section == RHS
                  || m_section == RANGES  || m_section == BOUNDS)
                  m_is_new_format = true;
            }
         }
         else
         {
            m_is_new_format = true;
         }
      }
      s = &m_buf[1];

      /* At this point it is not clear if we have a indicator field.
       * If there is none (e.g. empty) f1 will be the first name field.
       * If there is one, f2 will be the first name field.
       *
       * Initially comment marks '$' ar only allowed in the beginning
       * of the 2nd and 3rd name field. We test all fields but the first.
       * This makes no difference, since if the $ is at the start of a value
       * field, the line will be errornous anyway.
       */
      do
      {
         if (0 == (m_f1 = strtok(s, " ")))
            break;

         if ((0 == (m_f2 = strtok(0, " "))) || (*m_f2 == '$'))
         {
            m_f2 = 0;
            break;
         }
         if (!strcmp(m_f2, "'MARKER'"))
            is_marker = true;

         if ((0 == (m_f3 = strtok(0, " "))) || (*m_f3 == '$'))
         {
            m_f3 = 0;
            break;
         }
         if (is_marker)
         {
            if (!strcmp(m_f3, "'INTORG'"))
               m_is_integer = true;
            else if (!strcmp(m_f3, "'INTEND'"))
               m_is_integer = false;
            else
               break; // unknown marker
         }

         if (!strcmp(m_f3, "'MARKER'"))
            is_marker = true;

         if ((0 == (m_f4 = strtok(0, " "))) || (*m_f4 == '$'))
         {
            m_f4 = 0;
            break;
         }
         if (is_marker)
         {
            if (!strcmp(m_f4, "'INTORG'"))
               m_is_integer = true;
            else if (!strcmp(m_f4, "'INTEND'"))
               m_is_integer = false;
            else
               break; // unknown marker
         }

         if ((0 == (m_f5 = strtok(0, " "))) || (*m_f5 == '$'))
            m_f5 = 0;
      }
      while(false);
   }
   while(is_marker);

   MSG_DEBUG( std::cerr
             << "DMPSIN02 -----------------------------------------------"
             << std::endl
             << "DMPSIN03 f0=" << ((m_f0 == 0) ? "nil" : m_f0) << std::endl
             << "DMPSIN04 f1=" << ((m_f1 == 0) ? "nil" : m_f1) << std::endl
             << "DMPSIN05 f2=" << ((m_f2 == 0) ? "nil" : m_f2) << std::endl
             << "DMPSIN06 f3=" << ((m_f3 == 0) ? "nil" : m_f3) << std::endl
             << "DMPSIN07 f4=" << ((m_f4 == 0) ? "nil" : m_f4) << std::endl
             << "DMPSIN08 f5=" << ((m_f5 == 0) ? "nil" : m_f5) << std::endl
             << "DMPSIN09 -----------------------------------------------"
             << std::endl;
   )

   return true;
}

/// Insert \p name as field 1 and shift all other fields up.
void MPSInput::insertName(const char* name, bool second)
{
   m_f5 = m_f4;
   m_f4 = m_f3;
   m_f3 = m_f2;

   if (second)
      m_f2 = name;
   else
   {
      m_f2 = m_f1;
      m_f1 = name;
   }
}

} // namespace soplex


void mqtt_ha_add_unique_id(JsonDocument& doc, char* item)
{
    char buffer[64];

    snprintf_P(buffer, sizeof(buffer), PSTR("%s %s"), haspDevice.get_hostname(), item);
    doc[FPSTR(FP_MQTT_HA_NAME)] = buffer;

    snprintf_P(buffer, sizeof(buffer), PSTR("hasp_%s-%s"), HASP_MAC_ADDRESS, item);
    doc[F("uniq_id")] = buffer;
}

/*
   For full license information read the LICENSE file in the project folder */

#include "ArduinoJson.h"
#include "hasp_conf.h"

#if HASP_USE_MQTT > 0

#include "hasp/hasp.h"
#include "hasp/hasp_dispatch.h"
#include "hasp/hasp_parser.h"
#include "dev/device.h"

#include "hasp_mqtt.h"
#include "hasp_mqtt_ha.h"

#define RETAINED true

#if defined(WINDOWS) || defined(POSIX)
extern std::string mqttNodeTopic;
extern std::string mqttGroupTopic;
#else
extern char mqttNodeTopic[];
extern char mqttGroupTopic[];
#endif

extern bool mqttEnabled;
extern bool mqttHAautodiscover;

char discovery_prefix[] = "homeassistant";

const char FP_MQTT_HA_DEVICE[] PROGMEM       = "device";
const char FP_MQTT_HA_IDENTIFIERS[] PROGMEM  = "ids";
const char FP_MQTT_HA_NAME[] PROGMEM         = "name";
const char FP_MQTT_HA_MODEL[] PROGMEM        = "mdl";
const char FP_MQTT_HA_MANUFACTURER[] PROGMEM = "mf";

#if !(defined(WINDOWS) || defined(POSIX))

#include "hal/hasp_hal.h"

#define HASP_MAC_ADDRESS halGetMacAddress(0, "").c_str()
#define HASP_MAC_ADDRESS_STR halGetMacAddress(0, "")

// extern PubSubClient mqttClient;

#else

#define HASP_MAC_ADDRESS "aabbccddeeff"
#define HASP_MAC_ADDRESS_STR "aabbccddeeff"

#endif

void mqtt_ha_send_json(char* topic, JsonDocument& doc)
{
    LOG_VERBOSE(TAG_MQTT_PUB, topic);

    // size_t n;

    char buffer[800];
    size_t len = serializeJson(doc, buffer, sizeof(buffer));
    mqttPublish(topic, buffer, len, RETAINED);
}

// adds the device identifiers to the HA MQTT auto-discovery message
void mqtt_ha_add_device_ids(JsonDocument& doc)
{
    JsonObject device = doc.createNestedObject(FPSTR(FP_MQTT_HA_DEVICE));
    JsonArray ids     = device.createNestedArray(FPSTR(FP_MQTT_HA_IDENTIFIERS));
    ids.add(haspDevice.get_hostname());
    ids.add(HASP_MAC_ADDRESS_STR);

    device[F("sw")]                        = haspDevice.get_version();
    device[FPSTR(FP_MQTT_HA_NAME)]         = haspDevice.get_hostname();
    device[FPSTR(FP_MQTT_HA_MODEL)]        = F(PIOENV);
    device[FPSTR(FP_MQTT_HA_MANUFACTURER)] = F(D_MANUFACTURER);

    doc[F("~")] = mqttNodeTopic;
}

// adds the name and unique_id to the HA MQTT auto-discovery message

void mqtt_ha_register_button(uint8_t page, uint8_t id)
{
    StaticJsonDocument<800> doc;
    mqtt_ha_add_device_ids(doc);

    char buffer[128];
    snprintf_P(buffer, sizeof(buffer), PSTR(HASP_OBJECT_NOTATION), page, id);
    doc[F("stype")] = buffer; // subtype = "p0b0"
    snprintf_P(buffer, sizeof(buffer), PSTR("~" MQTT_TOPIC_STATE "/" HASP_OBJECT_NOTATION), page, id);
    doc[F("t")] = buffer; // topic

    doc[F("atype")] = "trigger"; // automation_type

    Parser::get_event_name(HASP_EVENT_DOWN, buffer, sizeof(buffer));
    doc[F("pl")]   = buffer;
    doc[F("type")] = "button_short_press";
    snprintf_P(buffer, sizeof(buffer), PSTR("%s/device_automation/%s/" HASP_OBJECT_NOTATION "_%s/config"),
               discovery_prefix, haspDevice.get_hostname(), page, id, "short_press");
    mqtt_ha_send_json(buffer, doc);

    Parser::get_event_name(HASP_EVENT_UP, buffer, sizeof(buffer));
    doc[F("pl")]   = buffer;
    doc[F("type")] = "button_short_release";
    snprintf_P(buffer, sizeof(buffer), PSTR("%s/device_automation/%s/" HASP_OBJECT_NOTATION "_%s/config"),
               discovery_prefix, haspDevice.get_hostname(), page, id, "short_release");
    mqtt_ha_send_json(buffer, doc);

    Parser::get_event_name(HASP_EVENT_LONG, buffer, sizeof(buffer));
    doc[F("pl")]   = buffer;
    doc[F("type")] = "button_long_press";
    snprintf_P(buffer, sizeof(buffer), PSTR("%s/device_automation/%s/" HASP_OBJECT_NOTATION "_%s/config"),
               discovery_prefix, haspDevice.get_hostname(), page, id, "long_press");
    mqtt_ha_send_json(buffer, doc);

    Parser::get_event_name(HASP_EVENT_RELEASE, buffer, sizeof(buffer));
    doc[F("pl")]   = buffer;
    doc[F("type")] = "button_long_release";
    snprintf_P(buffer, sizeof(buffer), PSTR("%s/device_automation/%s/" HASP_OBJECT_NOTATION "_%s/config"),
               discovery_prefix, haspDevice.get_hostname(), page, id, "long_release");
    mqtt_ha_send_json(buffer, doc);
}

void mqtt_ha_register_switch(uint8_t page, uint8_t id)
{
    StaticJsonDocument<800> doc;
    mqtt_ha_add_device_ids(doc);

    char buffer[128];
    snprintf_P(buffer, sizeof(buffer), PSTR(HASP_OBJECT_NOTATION), page, id);
    doc[F("stype")] = buffer; // subtype = "p0b0"
    snprintf_P(buffer, sizeof(buffer), PSTR("~" MQTT_TOPIC_STATE "/" HASP_OBJECT_NOTATION), page, id);
    doc[F("t")] = buffer; // topic

    doc[F("atype")] = F("binary_sensor"); // automation_type
    doc[F("pl")]    = F("short");         // payload
    doc[F("type")]  = F("button_short_release");

    snprintf_P(buffer, sizeof(buffer), PSTR("%s/device_automation/%s/" HASP_OBJECT_NOTATION "_%s/config"),
               discovery_prefix, haspDevice.get_hostname(), page, id, "short");

    mqtt_ha_send_json(buffer, doc);
}

void mqtt_ha_register_connectivity()
{
    StaticJsonDocument<1024> doc;
    char item[16];
    snprintf_P(item, sizeof(item), PSTR("connectivity"));

    // start from static keys and values that do not change
    deserializeJson(doc, F("{\"device_class\":\"connectivity\",\"stat_t\":\"~LWT\",\"pl_on\":\"online\",\"pl_off\":"
                           "\"offline\",\"json_attr_t\":\"~" MQTT_TOPIC_STATE "/statusupdate\"}"));
    mqtt_ha_add_device_ids(doc);
    mqtt_ha_add_unique_id(doc, item);

    char buffer[128];
    snprintf_P(buffer, sizeof(buffer), PSTR("%s/binary_sensor/%s/%s/config"), discovery_prefix,
               haspDevice.get_hostname(), item);
    mqtt_ha_send_json(buffer, doc);
}

void mqtt_ha_register_backlight()
{
    DynamicJsonDocument doc(1024);
    char item[16];
    snprintf_P(item, sizeof(item), PSTR("backlight"));

    // start from static keys and values that do not change
    deserializeJson(doc, F("{"
                           "\"cmd_t\":\"~" MQTT_TOPIC_COMMAND "/light\","
                           "\"stat_t\":\"~" MQTT_TOPIC_STATE "/light\","
                           "\"pl_on\":\"on\","
                           "\"pl_off\":\"off\","
                           "\"avty_t\":\"~LWT\","
                           "\"bri_stat_t\":\"~" MQTT_TOPIC_STATE "/dim\","
                           "\"bri_cmd_t\":\"~" MQTT_TOPIC_COMMAND "/dim\","
                           "\"bri_scl\":100}"));
    mqtt_ha_add_device_ids(doc);
    mqtt_ha_add_unique_id(doc, item);

    char buffer[128];
    snprintf_P(buffer, sizeof(buffer), PSTR("%s/light/%s/%s/config"), discovery_prefix, haspDevice.get_hostname(),
               item);
    mqtt_ha_send_json(buffer, doc);
}

void mqtt_ha_register_moodlight()
{
    DynamicJsonDocument doc(1024);
    char item[16];
    snprintf_P(item, sizeof(item), PSTR("moodlight"));

    // start from static keys and values that do not change
    deserializeJson(doc, F("{"
                           "\"cmd_t\":\"~" MQTT_TOPIC_COMMAND "/moodlight\","
                           "\"stat_t\":\"~" MQTT_TOPIC_STATE "/moodlight\","
                           "\"platform\":\"mqtt\","
                           "\"schema\":\"json\","
                           "\"rgb\":true,"
                           "\"brightness\":true,"
                           "\"avty_t\":\"~LWT\"}"));

    /*    deserializeJson(doc, F("{"
                               "\"cmd_t\":\"~command/moodlight\","
                               "\"avty_t\":\"~LWT\","
                               "\"bri_stat_t\":\"~" MQTT_TOPIC_STATE "/moodlight/dim\","
                               "\"bri_cmd_t\":\"~command/moodlight/dim\","
                               "\"bri_scl\":100,"
                               "\"rgb_stat_t\":\"~" MQTT_TOPIC_STATE "/moodlight/rgb\","
                               "\"rgb_cmd_t\":\"~command/moodlight/rgb\"}"));
                               */
    mqtt_ha_add_device_ids(doc);
    mqtt_ha_add_unique_id(doc, item);

    char buffer[128];
    snprintf_P(buffer, sizeof(buffer), PSTR("%s/light/%s/%s/config"), discovery_prefix, haspDevice.get_hostname(),
               item);
    mqtt_ha_send_json(buffer, doc);
}

void mqtt_ha_register_idle()
{
    StaticJsonDocument<800> doc;
    char item[16];
    snprintf_P(item, sizeof(item), PSTR("idle"));

    // start from static keys and values that do not change
    deserializeJson(doc, F("{\"stat_t\":\"~" MQTT_TOPIC_STATE
                           "/idle\",\"avty_t\":\"~LWT\",\"json_attr_t\":\"~" MQTT_TOPIC_STATE "/statusupdate\"}"));
    mqtt_ha_add_device_ids(doc);
    mqtt_ha_add_unique_id(doc, item);

    char buffer[128];
    snprintf_P(buffer, sizeof(buffer), PSTR("%s/sensor/%s/%s/config"), discovery_prefix, haspDevice.get_hostname(),
               item);
    mqtt_ha_send_json(buffer, doc);
}

void mqtt_ha_register_activepage()
{
    StaticJsonDocument<800> doc;
    char item[16];
    snprintf_P(item, sizeof(item), PSTR("page"));

    // start from static keys and values that do not change
    deserializeJson(doc, F("{\"cmd_t\":\"~" MQTT_TOPIC_COMMAND "/page\",\"stat_t\":\"~" MQTT_TOPIC_STATE
                           "/page\",\"avty_t\":\"~LWT\"}"));
    mqtt_ha_add_device_ids(doc);
    mqtt_ha_add_unique_id(doc, item);

    char buffer[128];
    snprintf_P(buffer, sizeof(buffer), PSTR("%s/number/%s/%s/config"), discovery_prefix, haspDevice.get_hostname(),
               item);
    mqtt_ha_send_json(buffer, doc);
}

void mqtt_ha_register_auto_discovery()
{
    LOG_TRACE(TAG_MQTT_PUB, F(D_MQTT_HA_AUTO_DISCOVERY));
    mqtt_ha_register_activepage();
    mqtt_ha_register_backlight();
    mqtt_ha_register_moodlight();
    mqtt_ha_register_idle();
    mqtt_ha_register_connectivity();
}
#endif

/*

homeassistant/binary_sensor/hasptest/idlestate/config

name: hasptest Touch
state_topic: hasp/hasptest/state/idle
value_template: '{% if value == "OFF" %}ON{% else %}OFF{% endif %}'
device_class: moving
json_attributes_topic: hasp/hasptest/state/statusupdate
availability_topic: "hasp/hasptest/LWT"
unique_id: 'hasptest_idlestate'
device:
  identifiers:
    - 'hasptest'
  name: 'HASP Test'
  model: 'hasptest'
  sw_version: 'v0.3.2'
  manufacturer: hasp-lvg

name: 'HASP hasptest Connectivity'
state_topic: "hasp/hasptest/LWT"
payload_on: "online"
payload_off: "offline"
device_class: connectivity
json_attributes_topic: "hasp/hasptest/state/statusupdate"
unique_id: 'hasptest_connectivity'
device:
  identifiers:
    - 'hasptest'
  name: 'HASP Test'
  model: 'hasptest'
  sw_version: 'v0.3.2'
  manufacturer: openHASP

{
    "device":
        {"ids": "plate_87546c", "name": "Test Switchplate", "mdl": "Lanbon L8", "sw": "v0.3.1", "mf": "openHASP"},
    "name": "Backlight",
    "uniq_id": "hasp35_light",
    "~": "hasp/plate35",
    "cmd_t": "~/command/light",
    "stat_t": "~/state/light",
    "avty_t": "~/LWT",
    "bri_stat_t": "~/state/dim",
    "bri_cmd_t": "~/command/dim",
    "bri_scl": 100,
    "pl_on": "on",
    "pl_off": "off"
}

{
    "name": "Lanbon idle State",
    "state_topic": "hasp/lanbon/state/idle",
    "value_template": "{{ value | capitalize }}",
    "icon": "hass:card",
    "json_attributes_topic": "hasp/lanbon/state/statusupdate",
    "availability_topic": "hasp/lanbon/LWT",
    "unique_id": "plate_87546c_idle",
    "device": {
        "identifiers": ["plate_87546c"],
        "name": "Test Switchplate",
        "model": "Lanbon L8",
        "sw_version": "v0.3.1",
        "manufacturer": "openHASP"
    }
}

{
    "name" : "Lanbon Backlight",
             "state_topic" : "hasp/lanbon/state/light",
                             "command_topic" : "hasp/lanbon/command/light",
                                               "brightness_state_topic" : "hasp/lanbon/state/dim",
                                                                          "brightness_scale" : 100,
                                                                          "brightness_command_topic"
        : "hasp/lanbon/command/dim",
          "availability_topic" : "hasp/lanbon/LWT",
                                 "unique_id" : "plate_87546c_bcklght",
                                               "device":
    {
        "identifiers" : ["plate_87546c"]
    }
}
*/


switch (data_type) {
        case kTengineFloat32:
//                          "Interal error: Java float not compatible with "
            return 4;

        case kTengineFloat16:
            return 2;

        case kTengineInt8:
        case kTengineUInt8:
            return 1;

        case kTengineInt32:
            return 4;

        case kTengineInt16:
            return 2;

        default:
            return 0;
    }

// Created by kris on 2019/8/20.
// @author example@nnthink.com

#include "tensor_jni.h"
#include "c_api_internal.h"
#include "interpreter.h"
#include "tensor.h"
#include "jni_utils.h"
#include "tengine_operations.h"

using aoetengine::jni::ThrowException;

TensorHandle *castToTensor(JNIEnv *env, long handle) {
    if (handle == 0 && NULL != env) {
        ThrowException(env, kIllegalArgumentException,
                       "Internal error: Invalid handle to TensorHandle.");
        return nullptr;
    }
    return reinterpret_cast<TensorHandle *>(handle);
}

JNIEXPORT jlong JNICALL
TENGINEJNI_METHOD2(create)(JNIEnv *env, jclass clazz, jlong interpreterHandle, jint tensorIndex,
                        jboolean isInput) {
    InterpreterHandler *interpreter = reinterpret_cast<InterpreterHandler *>(interpreterHandle);
    if (isInput) {
        return interpreter->inputs[tensorIndex];
    }

    return interpreter->outputs[tensorIndex];
}

JNIEXPORT void JNICALL
TENGINEJNI_METHOD2(delete)(JNIEnv *env, jclass clazz, jlong handle) {
    delete reinterpret_cast<TensorHandle *>(handle);
}

JNIEXPORT jint JNICALL
TENGINEJNI_METHOD2(inputRgbaResize)(JNIEnv *env, jclass clazz, jbyteArray rgbaDate,
                                 jint srcWidth, jint srcHeight, jint dstWidth, jint dstHeight,
                                 jfloatArray channelMeanVals, jboolean toBgr, jfloat scale,
                                 jlong handler) {
    TensorHandle *tensor = castToTensor(env, handler);
    int ret = tensor->checkBufferAndMalloc(sizeof(float) * dstHeight * dstWidth * 3);
    if (ret != 0) {
        // buffer error
        return ret;
    }

    jbyte *imageDataByte = env->GetByteArrayElements(rgbaDate, NULL);
    jfloat *meanValsElements = NULL;
    if (NULL != channelMeanVals) {
        meanValsElements = env->GetFloatArrayElements(channelMeanVals, NULL);
    }

    int c = 3;
    const int inputC = 4;
    int h = srcHeight;
    int w = srcWidth;
    int dst_index;
    int src_index;

    unsigned char *imageData = (unsigned char *) imageDataByte;
    image imgT = make_image(w, h, c);
    for (int k = 0; k < c; ++k) {
        for (int j = 0; j < h; ++j) {
            for (int i = 0; i < w; ++i) {
                dst_index = i + w * j + w * h * k;
                src_index = k + inputC * i + inputC * w * j;
                imgT.data[dst_index] = (float) (imageData[src_index]);
            }
        }
    }

    image resImg = resize_image(imgT, dstWidth, dstHeight);
    free_image(imgT);
    if (toBgr) {
        resImg = rgb2bgr_premute(resImg);
    }

    float *img_data = resImg.data;
    int hw = dstHeight * dstWidth;

    float *input_data = static_cast<float *>(tensor->getTensorBuffer());
    if (NULL != meanValsElements) {
        for (int c = 0; c < 3; c++)
            for (int h = 0; h < dstHeight; h++)
                for (int w = 0; w < dstWidth; w++) {
                    // input_data为整个channel b  整个channel g 整个channel r
                    input_data[c * hw + h * dstWidth + w] =
                            (*img_data - meanValsElements[c]) * scale;
                    img_data++;
                }
    }
    free_image(resImg);

    env->ReleaseByteArrayElements(rgbaDate, imageDataByte, 0);
    if (NULL != meanValsElements) {
        env->ReleaseFloatArrayElements(channelMeanVals, meanValsElements, 0);
    }

    return 0;
}

JNIEXPORT void JNICALL
TENGINEJNI_METHOD2(writeDirectBuffer)(JNIEnv *env, jclass clazz, jlong handle, jobject src) {
    TensorHandle *tensor = castToTensor(env, handle);
    if (tensor == nullptr) return;

    char *src_data_raw = static_cast<char *>(env->GetDirectBufferAddress(src));
    if (!src_data_raw) {
        ThrowException(env, kIllegalArgumentException,
                       "Input ByteBuffer is not a direct buffer");
        return;
    }

    tensor->setTensorBuffer(src_data_raw, env->GetDirectBufferCapacity(src));
}

JNIEXPORT jobject JNICALL
TENGINEJNI_METHOD2(buffer)(JNIEnv *env, jclass clazz, jlong handler) {
    TensorHandle *tensor = castToTensor(env, handler);
    tensor->checkBufferAndMalloc();
    if (tensor->getTensorBuffer() == nullptr) {
        ThrowException(env, kIllegalArgumentException,
                       "Internal error: Tensor hasn't been allocated.");
        return nullptr;
    }

    return env->NewDirectByteBuffer(tensor->getTensorBuffer(),
                                    static_cast<jlong>(tensor->getTensorBufferSize()));
}

JNIEXPORT void JNICALL
TENGINEJNI_METHOD2(resizeInputShape)(JNIEnv *env, jclass clazz, jintArray dims, jlong handle) {
    TensorHandle *tensor = castToTensor(env, handle);
    if (tensor == nullptr) return;

    int *dimElements = env->GetIntArrayElements(dims, NULL);
    tensor->reshape(dimElements);

    if (NULL != dimElements) {
        env->ReleaseIntArrayElements(dims, dimElements, 0);
    }
}

JNIEXPORT jintArray JNICALL
TENGINEJNI_METHOD2(shape)(JNIEnv *env, jclass clazz, jlong handle) {
    TensorHandle *tensor = castToTensor(env, handle);
    if (tensor == nullptr) return nullptr;

    int num_dims = 4;
    int dimsArray[num_dims];
    dimsArray[0] = tensor->getShapeW();
    dimsArray[1] = tensor->getShapeH();
    dimsArray[2] = tensor->getShapeC();
    dimsArray[3] = tensor->getShapeDim();

    jintArray result = env->NewIntArray(num_dims);
    env->SetIntArrayRegion(result, 0, num_dims, dimsArray);
    return result;
}

JNIEXPORT jint JNICALL
TENGINEJNI_METHOD2(numBytes)(JNIEnv *env, jclass clazz, jlong handle) {
    TensorHandle *tensor = castToTensor(env, handle);
    if (tensor == nullptr) return 0;
    return static_cast<jint>(tensor->getTensorBufferSize());
}

size_t ElementByteSize(TengineType data_type) {
    // The code in this file makes the assumption that the
    // TensorFlow TF_DataTypes and the Java primitive types
    // have the same byte sizes. Validate that:

}

size_t ReadOneDimensionalArray(JNIEnv *env, TengineType data_type,
                               const void *src, size_t src_size, jarray dst) {
    const int len = env->GetArrayLength(dst);
    const size_t size = len * ElementByteSize(data_type);
    if (size > src_size) {
        ThrowException(
                env, kIllegalStateException,
                "Internal error: cannot fill a Java array of %d bytes with a ncnn of "
                "%d bytes",
                size, src_size);
        return 0;
    }

    switch (data_type) {
        case kTengineFloat32: {
            jfloatArray float_array = static_cast<jfloatArray>(dst);
            env->SetFloatArrayRegion(float_array, 0, len,
                                     static_cast<const jfloat *>(src));
            return size;
        }
        case kTengineInt32: {
            jintArray int_array = static_cast<jintArray>(dst);
            env->SetIntArrayRegion(int_array, 0, len, static_cast<const jint *>(src));
            return size;
        }

        case kTengineInt16: {
            jshortArray short_array = static_cast<jshortArray>(dst);
            env->SetShortArrayRegion(short_array, 0, len,
                                     static_cast<const jshort *>(src));
            return size;
        }

        case kTengineUInt8:
        case kTengineInt8: {
            jbyteArray byte_array = static_cast<jbyteArray>(dst);
            env->SetByteArrayRegion(byte_array, 0, len,
                                    static_cast<const jbyte *>(src));
            return size;
        }
        default: {
            ThrowException(env, kIllegalStateException,
                           "DataType error: invalid DataType(%d)", data_type);
        }
    }
    return 0;
}

size_t ReadMultiDimensionalArray(JNIEnv *env, TengineType data_type, char *src,
                                 size_t src_size, int dims_left, jarray dst) {
    if (dims_left == 1) {
        return ReadOneDimensionalArray(env, data_type, src, src_size, dst);
    } else {
        jobjectArray ndarray = static_cast<jobjectArray>(dst);
        int len = env->GetArrayLength(ndarray);
        size_t size = 0;
        for (int i = 0; i < len; ++i) {
            jarray row = static_cast<jarray>(env->GetObjectArrayElement(ndarray, i));
            size += ReadMultiDimensionalArray(env, data_type, src + size,
                                              src_size - size, dims_left - 1, row);
            env->DeleteLocalRef(row);
            if (env->ExceptionCheck()) return size;
        }
        return size;
    }
}

JNIEXPORT void JNICALL
TENGINEJNI_METHOD2(readMultiDimensionalArray)(JNIEnv *env, jclass clazz, jlong handler,
                                           jobject value) {
    TensorHandle *tensor = castToTensor(env, handler);
    if (tensor->getShapeDim() == 0) {
        ThrowException(env, kIllegalArgumentException,
                       "Internal error: Cannot copy empty/scalar Tensors.");
        return;
    }

    ReadMultiDimensionalArray(env, tensor->getDataType(), (char *) tensor->getTensorBuffer(),
                              tensor->getTensorBufferSize(),
                              tensor->getShapeDim(),
                              static_cast<jarray>(value));
}

size_t WriteOneDimensionalArray(JNIEnv *env, jobject object, TengineType type,
                                void *dst, size_t dst_size) {
    jarray array = static_cast<jarray>(object);
    const int num_elements = env->GetArrayLength(array);
    size_t to_copy = num_elements * ElementByteSize(type);
    if (to_copy > dst_size) {
        ThrowException(env, kIllegalStateException,
                       "Internal error: cannot write Java array of %d bytes to "
                       "Tensor of %d bytes",
                       to_copy, dst_size);
        return 0;
    }
    switch (type) {
        case kTengineFloat32: {
            jfloatArray float_array = static_cast<jfloatArray>(array);
            jfloat *float_dst = static_cast<jfloat *>(dst);
            env->GetFloatArrayRegion(float_array, 0, num_elements, float_dst);
            return to_copy;
        }

        case kTengineInt8:
        case kTengineUInt8: {
            jbyteArray byte_array = static_cast<jbyteArray>(array);
            jbyte *byte_dst = static_cast<jbyte *>(dst);
            env->GetByteArrayRegion(byte_array, 0, num_elements, byte_dst);
            return to_copy;
        }
        case kTengineInt32: {
            jintArray int_array = static_cast<jintArray>(array);
            jint *int_dst = static_cast<jint *>(dst);
            env->GetIntArrayRegion(int_array, 0, num_elements, int_dst);
            return to_copy;
        }
        case kTengineInt16: {
            jshortArray int_array = static_cast<jshortArray>(array);
            jshort *int_dst = static_cast<jshort *>(dst);
            env->GetShortArrayRegion(int_array, 0, num_elements, int_dst);
            return to_copy;
        }
        default: {
            ThrowException(env, kUnsupportedOperationException,
                           "DataType error: currently supports float "
                           "(32 bits), int (32 bits), byte (8 bits) "
                           "support for other types (DataType %d in this "
                           "case) will be added in the future",
                           type);
            return 0;
        }
    }
}

size_t WriteMultiDimensionalArray(JNIEnv *env, jobject src, TengineType type,
                                  int dims_left, char **dst, int dst_size) {
    if (dims_left <= 1) {
        return WriteOneDimensionalArray(env, src, type, *dst, dst_size);
    } else {
        jobjectArray ndarray = static_cast<jobjectArray>(src);
        int len = env->GetArrayLength(ndarray);
        size_t sz = 0;
        for (int i = 0; i < len; ++i) {
            jobject row = env->GetObjectArrayElement(ndarray, i);
            char *next_dst = *dst + sz;
            sz += WriteMultiDimensionalArray(env, row, type, dims_left - 1, &next_dst,
                                             dst_size - sz);
            env->DeleteLocalRef(row);
            if (env->ExceptionCheck()) return sz;
        }
        return sz;
    }
}

JNIEXPORT void JNICALL
TENGINEJNI_METHOD2(writeMultiDimensionalArray)(JNIEnv *env, jclass clazz, jlong handler, jobject src) {
    TensorHandle *tensor = castToTensor(env, handler);
    if (tensor == nullptr) return;
    if (tensor->getTensorBuffer() == nullptr) {
        ThrowException(env, kIllegalArgumentException,
                       "Internal error: Target Tensor hasn't been allocated.");
        return;
    }

    if (tensor->getShapeDim() == 0) {
        ThrowException(env, kIllegalArgumentException,
                       "Internal error: Cannot copy empty/scalar Tensors.");
        return;
    }

    char *data = (char *) tensor->getTensorBuffer();
    WriteMultiDimensionalArray(env, src, tensor->getDataType(), tensor->getShapeDim(),
                               &data, tensor->getTensorBufferSize());
}