/*
 *  Copyright (c) 2016, The OpenThread Authors.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. Neither the name of the copyright holder nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

#include "pch.h"
#include "MainPage.xaml.h"

using namespace Thread;

using namespace Platform;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Controls::Primitives;
using namespace Windows::UI::Xaml::Data;
using namespace Windows::UI::Xaml::Input;
using namespace Windows::UI::Xaml::Media;
using namespace Windows::UI::Xaml::Navigation;

MainPage^ MainPage::Current = nullptr;

#define GUID_FORMAT L"{%08lX-%04hX-%04hX-%02hhX%02hhX-%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX}"
#define GUID_ARG(guid) guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]

#define MAC8_FORMAT L"%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X"
#define MAC8_ARG(mac) mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], mac[6], mac[7]

PCWSTR ToString(otDeviceRole role)
{
    switch (role)
    {
    case kDeviceRoleOffline:    return L"Offline";
    case kDeviceRoleDisabled:   return L"Disabled";
    case kDeviceRoleDetached:   return L"Disconnected";
    case kDeviceRoleChild:      return L"Connected - Child";
    case kDeviceRoleRouter:     return L"Connected - Router";
    case kDeviceRoleLeader:     return L"Connected - Leader";
    }

    return L"Unknown Role State";
}

void OTCALL
ThreadDeviceAvailabilityCallback(
    bool        /* aAdded */, 
    const GUID* /* aDeviceGuid */, 
    _In_ void*  /* aContext */
    )
{
    // Trigger the interface list to update
    MainPage::Current->Dispatcher->RunAsync(
        Windows::UI::Core::CoreDispatcherPriority::Normal,
        ref new Windows::UI::Core::DispatchedHandler(
            [=]() {
                MainPage::Current->BuildInterfaceList();
            }
        )
    );
}

void OTCALL
ThreadStateChangeCallback(
    uint32_t aFlags, 
    _In_ void* /* aContext */
    )
{
    if ((aFlags & OT_NET_ROLE) != 0)
    {
        // Trigger the interface list to update
        MainPage::Current->Dispatcher->RunAsync(
            Windows::UI::Core::CoreDispatcherPriority::Normal,
            ref new Windows::UI::Core::DispatchedHandler(
                [=]() {
                    MainPage::Current->BuildInterfaceList();
                }
            )
        );
    }
}

MainPage::MainPage()
{
    InitializeComponent();

    MainPage::Current = this;
    _isFullScreen = false;
    
    _apiInstance = nullptr;
    InterfaceConfigCancelButton->Click +=
        ref new RoutedEventHandler(
            [=](Platform::Object^, RoutedEventArgs^) {
                InterfaceConfiguration->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
            }
    );
    InterfaceConfigOkButton->Click +=
        ref new RoutedEventHandler(
            [=](Platform::Object^, RoutedEventArgs^) {
                InterfaceConfiguration->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
                ConnectNetwork(_currentInterface);
            }
    );
    InterfaceDetailsCloseButton->Click +=
        ref new RoutedEventHandler(
            [=](Platform::Object^, RoutedEventArgs^) {
                InterfaceDetails->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
            }
    );
}

void MainPage::OnNavigatedTo(NavigationEventArgs^ e)
{
    Window::Current->CoreWindow->VisibilityChanged += ref new TypedEventHandler<Windows::UI::Core::CoreWindow^, Windows::UI::Core::VisibilityChangedEventArgs^>(this, &MainPage::OnVisibilityChanged);

    SizeChanged += ref new SizeChangedEventHandler(this, &MainPage::OnWindowSizeChanged);
    Loaded += ref new RoutedEventHandler(this, &MainPage::OnLoaded);
    Unloaded += ref new RoutedEventHandler(this, &MainPage::OnUnloaded);

    _isVisible = Window::Current->CoreWindow->Visible;
}

void MainPage::OnLoaded(Object^ sender, RoutedEventArgs^ e)
{
    // Initialize api handle
    _apiInstance = otApiInit();
    if (_apiInstance)
    {
        // Register for device availability callbacks
        otSetDeviceAvailabilityChangedCallback(ApiInstance, ThreadDeviceAvailabilityCallback, nullptr);

        // Build the initial list
        BuildInterfaceList();
    }
}

void MainPage::OnUnloaded(Object^ sender, RoutedEventArgs^ e)
{
    // Unregister for callbacks
    otSetDeviceAvailabilityChangedCallback(ApiInstance, nullptr, nullptr);

    // Clean up api handle
    otApiFinalize(ApiInstance);
    _apiInstance = nullptr;
}

void MainPage::OnResuming()
{
}

void MainPage::OnWindowSizeChanged(Object^ sender, SizeChangedEventArgs^ args)
{
    _windowSize = args->NewSize;
}

void MainPage::OnVisibilityChanged(Windows::UI::Core::CoreWindow^ coreWindow, Windows::UI::Core::VisibilityChangedEventArgs^ args)
{
    // The Visible property is toggled when the app enters and exits minimized state.
    // But obscuring the app with another window does not change Visible state, oddly enough.
    _isVisible = args->Visible;
}

void MainPage::ShowInterfaceDetails(Platform::Guid InterfaceGuid)
{
    if (ApiInstance == nullptr) return;

    GUID deviceGuid = InterfaceGuid;
    auto device = otInstanceInit(ApiInstance, &deviceGuid);

    auto extendedAddress = otGetExtendedAddress(device);
    if (extendedAddress)
    {
        WCHAR szMac[256] = { 0 };
        swprintf_s(szMac, 256, MAC8_FORMAT, MAC8_ARG(extendedAddress));
        InterfaceMacAddress->Text = ref new String(szMac);

        otFreeMemory(extendedAddress);
    }
    else
    {
        InterfaceMacAddress->Text = L"ERROR";
    }

    auto ml_eid = otGetMeshLocalEid(device);
    if (ml_eid)
    {
        WCHAR szAddress[46] = { 0 };
        RtlIpv6AddressToStringW((const PIN6_ADDR)ml_eid, szAddress);
        InterfaceML_EID->Text = ref new String(szAddress);

        otFreeMemory(ml_eid);
    }
    else
    {
        InterfaceML_EID->Text = L"ERROR";
    }
    
    auto rloc16 = otGetRloc16(device);
    WCHAR szRloc[16] = { 0 };
    swprintf_s(szRloc, 16, L"%4x", rloc16);
    InterfaceRLOC->Text = ref new String(szRloc);

    if (otGetDeviceRole(device) > kDeviceRoleChild)
    {
        uint8_t index = 0;
        otChildInfo childInfo;
        while (kThreadError_None == otGetChildInfoByIndex(device, index, &childInfo))
        {
            index++;
        }

        WCHAR szText[64] = { 0 };
        swprintf_s(szText, 64, L"%d", index);
        InterfaceChildren->Text = ref new String(szText);

        InterfaceNeighbors->Text = L"unknown";

        InterfaceNeighbors->Visibility = Windows::UI::Xaml::Visibility::Visible;
        InterfaceNeighborsText->Visibility = Windows::UI::Xaml::Visibility::Visible;
        InterfaceChildren->Visibility = Windows::UI::Xaml::Visibility::Visible;
        InterfaceChildrenText->Visibility = Windows::UI::Xaml::Visibility::Visible;
    }

    // Show the details
    InterfaceDetails->Visibility = Windows::UI::Xaml::Visibility::Visible;

    otFreeMemory(device);
}

UIElement^ MainPage::CreateNewInterface(Platform::Guid InterfaceGuid)
{
    GUID deviceGuid = InterfaceGuid;

    auto device = otInstanceInit(ApiInstance, &deviceGuid);
    auto deviceRole = otGetDeviceRole(device);

    WCHAR szText[256] = { 0 };
    swprintf_s(szText, 256, L"%s\r\n\t" GUID_FORMAT L"\r\n\t%s",
        L"openthread interface", // TODO ...
        GUID_ARG(deviceGuid), 
        ::ToString(deviceRole));

    auto InterfaceStackPanel = ref new StackPanel();
    InterfaceStackPanel->Orientation = Orientation::Horizontal;

    auto InterfaceTextBlock = ref new TextBlock();
    InterfaceTextBlock->Text = ref new String(szText);
    InterfaceTextBlock->FontSize = 16;
    InterfaceTextBlock->Margin = Thickness(10);
    InterfaceTextBlock->TextWrapping = TextWrapping::Wrap;
    InterfaceStackPanel->Children->Append(InterfaceTextBlock);

    if (deviceRole == kDeviceRoleDisabled)
    {
        auto ConnectButton = ref new Button();
        ConnectButton->Content = ref new String(L"Connect");
        ConnectButton->Click +=
            ref new RoutedEventHandler(
                [=](Platform::Object^, RoutedEventArgs^) {
                    _currentInterface = InterfaceGuid;
                    InterfaceConfiguration->Visibility = Windows::UI::Xaml::Visibility::Visible;
                }
        );
        InterfaceStackPanel->Children->Append(ConnectButton);
    }
    else
    {
        auto DetailsButton = ref new Button();
        DetailsButton->Content = ref new String(L"Details");
        DetailsButton->Click +=
            ref new RoutedEventHandler(
                [=](Platform::Object^, RoutedEventArgs^) {
                    ShowInterfaceDetails(InterfaceGuid);
                }
        );
        InterfaceStackPanel->Children->Append(DetailsButton);

        auto DisconnectButton = ref new Button();
        DisconnectButton->Content = ref new String(L"Disconnect");
        DisconnectButton->Click +=
            ref new RoutedEventHandler(
                [=](Platform::Object^, RoutedEventArgs^) {
                    DisconnectNetwork(InterfaceGuid);
                }
        );
        InterfaceStackPanel->Children->Append(DisconnectButton);
    }

    // Register for callbacks on the device
    otSetStateChangedCallback(device, ThreadStateChangeCallback, nullptr);

    // Cache the device
    _devices.push_back(device);

    return InterfaceStackPanel;
}

void MainPage::BuildInterfaceList()
{
    if (ApiInstance == nullptr) return;

    // Clear all existing children
    InterfaceList->Items->Clear();

    // Clean up devices
    for each (auto device in _devices)
    {
        // Unregister for callbacks for the device
        otSetStateChangedCallback((otInstance*)device, nullptr, nullptr);

        // Free the device
        otFreeMemory(device);
    }
    _devices.clear();

    // Enumerate the new device list
    auto deviceList = otEnumerateDevices(ApiInstance);
    if (deviceList)
    {
        // Dump the results to the console
        for (DWORD dwIndex = 0; dwIndex < deviceList->aDevicesLength; dwIndex++)
        {
            InterfaceList->Items->Append(
                CreateNewInterface(deviceList->aDevices[dwIndex]));
        }

        otFreeMemory(deviceList);
    }
}

void MainPage::ConnectNetwork(Platform::Guid InterfaceGuid)
{
    if (ApiInstance == nullptr) return;

    GUID deviceGuid = InterfaceGuid;
    auto device = otInstanceInit(ApiInstance, &deviceGuid);

    //
    // Configure
    //

    otNetworkName networkName = {};
    wcstombs(networkName.m8, InterfaceConfigName->Text->Data(), sizeof(networkName.m8));
    otSetNetworkName(device, networkName.m8);

    otMasterKey masterKey = {};
    wcstombs((char*)masterKey.m8, InterfaceConfigKey->Text->Data(), sizeof(masterKey.m8));
    otSetMasterKey(device, masterKey.m8, sizeof(masterKey.m8));

    otSetChannel(device, (uint8_t)InterfaceConfigChannel->Value);
    otSetMaxAllowedChildren(device, (uint8_t)InterfaceConfigMaxChildren->Value);

    otSetPanId(device, 0x4567);

    //
    // Bring up the interface and start the Thread logic
    //

    otInterfaceUp(device);

    otThreadStart(device);

    // Cleanup
    otFreeMemory(device);
}

void MainPage::DisconnectNetwork(Platform::Guid InterfaceGuid)
{
    if (ApiInstance == nullptr) return;

    GUID deviceGuid = InterfaceGuid;
    auto device = otInstanceInit(ApiInstance, &deviceGuid);

    //
    // Start the Thread logic and the interface
    //

    otThreadStop(device);

    otInterfaceDown(device);

    // Cleanup
    otFreeMemory(device);
}
