/*
 *  Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "udp_socket_manager_wrapper.h"

#include <cassert>

#ifdef _WIN32
#include "fix_interlocked_exchange_pointer_windows.h"
#include "udp_socket_manager_windows.h"
#include "udp_socket2_manager_windows.h"
#else
#include "udp_socket_manager_posix.h"
#endif

#ifndef _WIN32
#include <memory>
#endif

namespace webrtc {
UdpSocketManager* UdpSocketManager::CreateSocketManager(
    const WebRtc_Word32 id,
    WebRtc_UWord8& numOfWorkThreads)
{
#if defined(_WIN32)
    #if (defined(USE_WINSOCK2))
        return static_cast<UdpSocketManager*>(
            new UdpSocket2ManagerWindows(id, numOfWorkThreads));
    #else
        numOfWorkThreads = 1;
        return static_cast<UdpSocketManager*>(
            new UdpSocketManagerWindows(id, numOfWorkThreads));
    #endif
#else
    return new UdpSocketManagerPosix(id, numOfWorkThreads);
#endif
}

// TODO (hellner): more or less the same code is used in trace_impl.cc.
// Should be possible to avoid duplication here.
// Construct On First Use idiom. Avoids "static initialization order fiasco".
UdpSocketManager* UdpSocketManager::StaticInstance(
    const UdpSocketManagerCount inc,
    const WebRtc_Word32 id,
    WebRtc_UWord8& numOfWorkThreads)
{
    // TODO (hellner): use atomic wrapper instead.
    static volatile long theUdpSocketManagerCount = 0;
    static UdpSocketManager* volatile theUdpSocketManager = NULL;

    UdpSocketManagerState state = kUdpSocketManagerExist;

#ifndef _WIN32

    static std::auto_ptr<CriticalSectionWrapper> crtiSect =
        std::auto_ptr<CriticalSectionWrapper>(
            CriticalSectionWrapper::CreateCriticalSection());
    CriticalSectionScoped lock(*crtiSect);

    if(inc == kUdpSocketManagerInc)
    {
        theUdpSocketManagerCount++;
        if(theUdpSocketManagerCount == 1)
        {
            state = kUdpSocketManagerCreate;
        }
    } else
    {
        theUdpSocketManagerCount--;
        if(theUdpSocketManagerCount == 0)
        {
            state = kUdpSocketManagerDestroy;
        }
    }
    if(state == kUdpSocketManagerCreate)
    {
        theUdpSocketManager =
            UdpSocketManager::CreateSocketManager(id, numOfWorkThreads);
        theUdpSocketManager->Start();
        assert(theUdpSocketManager);
        return theUdpSocketManager;

    }else if(state == kUdpSocketManagerDestroy)
    {
        UdpSocketManager* oldValue = theUdpSocketManager;
        theUdpSocketManager = NULL;
        if(oldValue)
        {
            if(oldValue->Stop())
            {
                delete static_cast<UdpSocketManager*>(oldValue);
            }
        }
        return NULL;
    } else
    {
        if(theUdpSocketManager)
        {
            numOfWorkThreads = theUdpSocketManager->WorkThreads();
        }
    }
#else // _WIN32
    if(inc == kUdpSocketManagerInc)
    {
        if(theUdpSocketManagerCount == 0)
        {
            state = kUdpSocketManagerCreate;
        }else {
            if(1 == InterlockedIncrement(&theUdpSocketManagerCount))
            {
                // The instance has been destroyed by some other thread.
                // Rollback.
                InterlockedDecrement(&theUdpSocketManagerCount);
                state = kUdpSocketManagerCreate;
            }
        }
    } else
    {
        WebRtc_Word32 newValue = InterlockedDecrement(
            &theUdpSocketManagerCount);
        if(newValue == 0)
        {
            state = kUdpSocketManagerDestroy;
        }
    }
    if(state == kUdpSocketManagerCreate)
    {
        // Create instance and let whichever thread finishes first assign its
        // local copy to the global instance. All other threads reclaim their
        // local copy.
        UdpSocketManager* newSocketMgr=
            UdpSocketManager::CreateSocketManager(id, numOfWorkThreads);
        if(1 == InterlockedIncrement(&theUdpSocketManagerCount))
        {
            UdpSocketManager* oldValue = (UdpSocketManager*)
                InterlockedExchangePointer(
                    reinterpret_cast<void* volatile*>(&theUdpSocketManager),
                    newSocketMgr);
            newSocketMgr->Start();
            assert(oldValue == NULL);
            assert(theUdpSocketManager);
            return newSocketMgr;

        }
        InterlockedDecrement(&theUdpSocketManagerCount);
        if(newSocketMgr)
        {
            delete static_cast<UdpSocketManager*>(newSocketMgr);
        }
        return NULL;
    } else if(state == kUdpSocketManagerDestroy)
    {
        UdpSocketManager* oldValue = (UdpSocketManager*)
            InterlockedExchangePointer(
                reinterpret_cast<void* volatile*>(&theUdpSocketManager),
                NULL);
        if(oldValue)
        {
            if(oldValue->Stop())
            {
                delete static_cast<UdpSocketManager*>(oldValue);
            }
        }
        return NULL;
    } else
    {
        if(theUdpSocketManager)
        {
            numOfWorkThreads = theUdpSocketManager->WorkThreads();
        }
    }
#endif // #ifndef _WIN32
    return theUdpSocketManager;
}

UdpSocketManager* UdpSocketManager::Create(const WebRtc_Word32 id,
                                           WebRtc_UWord8& numOfWorkThreads)
{
    return UdpSocketManager::StaticInstance(kUdpSocketManagerInc, id,
                                            numOfWorkThreads);
}

void UdpSocketManager::Return()
{
    WebRtc_UWord8 numOfWorkThreads = 0;
    UdpSocketManager::StaticInstance(kUdpSocketManagerDec, -1,
                                     numOfWorkThreads);
}

UdpSocketManager::UdpSocketManager(const WebRtc_Word32 /*id*/,
                                   WebRtc_UWord8& numOfWorkThreads)
    : _numOfWorkThreads(numOfWorkThreads)
{
}

WebRtc_UWord8 UdpSocketManager::WorkThreads() const
{
    return _numOfWorkThreads;
}
} // namespace webrtc
