/*
 *  Copyright (c) 2012 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.
 */

#ifndef WEBRTC_SYSTEM_WRAPPERS_INCLUDE_STATIC_INSTANCE_H_
#define WEBRTC_SYSTEM_WRAPPERS_INCLUDE_STATIC_INSTANCE_H_

#include <assert.h>

#include "webrtc/system_wrappers/include/critical_section_wrapper.h"
#ifdef _WIN32
#include "webrtc/system_wrappers/include/fix_interlocked_exchange_pointer_win.h"
#endif

namespace webrtc {

enum CountOperation {
  kRelease,
  kAddRef,
  kAddRefNoCreate
};
enum CreateOperation {
  kInstanceExists,
  kCreate,
  kDestroy
};

template <class T>
// Construct On First Use idiom. Avoids
// "static initialization order fiasco".
static T* GetStaticInstance(CountOperation count_operation) {
  // TODO (hellner): use atomic wrapper instead.
  static volatile long instance_count = 0;
  static T* volatile instance = NULL;
  CreateOperation state = kInstanceExists;
#ifndef _WIN32
  // This memory is staticly allocated once. The application does not try to
  // free this memory. This approach is taken to avoid issues with
  // destruction order for statically allocated memory. The memory will be
  // reclaimed by the OS and memory leak tools will not recognize memory
  // reachable from statics leaked so no noise is added by doing this.
  static CriticalSectionWrapper* crit_sect(
    CriticalSectionWrapper::CreateCriticalSection());
  CriticalSectionScoped lock(crit_sect);

  if (count_operation ==
      kAddRefNoCreate && instance_count == 0) {
    return NULL;
  }
  if (count_operation ==
      kAddRef ||
      count_operation == kAddRefNoCreate) {
    instance_count++;
    if (instance_count == 1) {
      state = kCreate;
    }
  } else {
    instance_count--;
    if (instance_count == 0) {
      state = kDestroy;
    }
  }
  if (state == kCreate) {
    instance = T::CreateInstance();
  } else if (state == kDestroy) {
    T* old_instance = instance;
    instance = NULL;
    // The state will not change past this point. Release the critical
    // section while deleting the object in case it would be blocking on
    // access back to this object. (This is the case for the tracing class
    // since the thread owned by the tracing class also traces).
    // TODO(hellner): this is a bit out of place but here goes, de-couple
    // thread implementation with trace implementation.
    crit_sect->Leave();
    if (old_instance) {
      delete old_instance;
    }
    // Re-acquire the lock since the scoped critical section will release
    // it.
    crit_sect->Enter();
    return NULL;
  }
#else  // _WIN32
  if (count_operation ==
      kAddRefNoCreate && instance_count == 0) {
    return NULL;
  }
  if (count_operation == kAddRefNoCreate) {
    if (1 == InterlockedIncrement(&instance_count)) {
      // The instance has been destroyed by some other thread. Rollback.
      InterlockedDecrement(&instance_count);
      assert(false);
      return NULL;
    }
    // Sanity to catch corrupt state.
    if (instance == NULL) {
      assert(false);
      InterlockedDecrement(&instance_count);
      return NULL;
    }
  } else if (count_operation == kAddRef) {
    if (instance_count == 0) {
      state = kCreate;
    } else {
      if (1 == InterlockedIncrement(&instance_count)) {
        // InterlockedDecrement because reference count should not be
        // updated just yet (that's done when the instance is created).
        InterlockedDecrement(&instance_count);
        state = kCreate;
      }
    }
  } else {
    int new_value = InterlockedDecrement(&instance_count);
    if (new_value == 0) {
      state = kDestroy;
    }
  }

  if (state == kCreate) {
    // Create instance and let whichever thread finishes first assign its
    // local copy to the global instance. All other threads reclaim their
    // local copy.
    T* new_instance = T::CreateInstance();
    if (1 == InterlockedIncrement(&instance_count)) {
      InterlockedExchangePointer(reinterpret_cast<void * volatile*>(&instance),
                                 new_instance);
    } else {
      InterlockedDecrement(&instance_count);
      if (new_instance) {
        delete static_cast<T*>(new_instance);
      }
    }
  } else if (state == kDestroy) {
    T* old_value = static_cast<T*>(InterlockedExchangePointer(
        reinterpret_cast<void * volatile*>(&instance), NULL));
    if (old_value) {
      delete static_cast<T*>(old_value);
    }
    return NULL;
  }
#endif  // #ifndef _WIN32
  return instance;
}

}  // namspace webrtc

#endif  // WEBRTC_SYSTEM_WRAPPERS_INCLUDE_STATIC_INSTANCE_H_
