/*
//@HEADER
// ************************************************************************
//
//                        Kokkos v. 2.0
//              Copyright (2014) Sandia Corporation
//
// Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
// the U.S. Government retains certain rights in this software.
//
// 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 Corporation nor the names of the
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY SANDIA CORPORATION "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 SANDIA CORPORATION OR THE
// 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.
//
// Questions? Contact Christian R. Trott (crtrott@sandia.gov)
//
// ************************************************************************
//@HEADER
*/

#include <Kokkos_Core.hpp>

namespace Kokkos {
namespace Impl {

__thread int SharedAllocationRecord<void, void>::t_tracking_enabled = 1;

#ifdef KOKKOS_DEBUG
bool
SharedAllocationRecord< void , void >::
is_sane( SharedAllocationRecord< void , void > * arg_record )
{
  SharedAllocationRecord * const root = arg_record ? arg_record->m_root : 0 ;

  bool ok = root != 0 && root->use_count() == 0 ;

  if ( ok ) {
    SharedAllocationRecord * root_next = 0 ;
    static constexpr SharedAllocationRecord * zero = nullptr;
    // Lock the list:
    while ( ( root_next = Kokkos::atomic_exchange( & root->m_next , zero ) ) == nullptr );

    for ( SharedAllocationRecord * rec = root_next ; ok && rec != root ; rec = rec->m_next ) {
      const bool ok_non_null  = rec && rec->m_prev && ( rec == root || rec->m_next );
      const bool ok_root      = ok_non_null && rec->m_root == root ;
      const bool ok_prev_next = ok_non_null && ( rec->m_prev != root ? rec->m_prev->m_next == rec : root_next == rec );
      const bool ok_next_prev = ok_non_null && rec->m_next->m_prev == rec ;
      const bool ok_count     = ok_non_null && 0 <= rec->use_count() ;

      ok = ok_root && ok_prev_next && ok_next_prev && ok_count ;

      if ( ! ok ) {
        //Formatting dependent on sizeof(uintptr_t)
        const char * format_string;

        if (sizeof(uintptr_t) == sizeof(unsigned long)) {
          format_string = "Kokkos::Impl::SharedAllocationRecord failed is_sane: rec(0x%.12lx){ m_count(%d) m_root(0x%.12lx) m_next(0x%.12lx) m_prev(0x%.12lx) m_next->m_prev(0x%.12lx) m_prev->m_next(0x%.12lx) }\n";
        }
        else if (sizeof(uintptr_t) == sizeof(unsigned long long)) {
          format_string = "Kokkos::Impl::SharedAllocationRecord failed is_sane: rec(0x%.12llx){ m_count(%d) m_root(0x%.12llx) m_next(0x%.12llx) m_prev(0x%.12llx) m_next->m_prev(0x%.12llx) m_prev->m_next(0x%.12llx) }\n";
        }

        fprintf(stderr
            , format_string
            , reinterpret_cast< uintptr_t >( rec )
            , rec->use_count()
            , reinterpret_cast< uintptr_t >( rec->m_root )
            , reinterpret_cast< uintptr_t >( rec->m_next )
            , reinterpret_cast< uintptr_t >( rec->m_prev )
            , reinterpret_cast< uintptr_t >( rec->m_next != NULL ? rec->m_next->m_prev : NULL )
            , reinterpret_cast< uintptr_t >( rec->m_prev != rec->m_root ? rec->m_prev->m_next : root_next )
            );
      }

    }

    if ( nullptr != Kokkos::atomic_exchange( & root->m_next , root_next ) ) {
      Kokkos::Impl::throw_runtime_exception("Kokkos::Impl::SharedAllocationRecord failed is_sane unlocking");
    }
  }
  return ok ;
}

#else

bool
SharedAllocationRecord< void , void >::
is_sane( SharedAllocationRecord< void , void > * )
{
  Kokkos::Impl::throw_runtime_exception("Kokkos::Impl::SharedAllocationRecord::is_sane only works with KOKKOS_DEBUG enabled");
  return false ;
}
#endif //#ifdef KOKKOS_DEBUG

#ifdef KOKKOS_DEBUG
SharedAllocationRecord<void,void> *
SharedAllocationRecord<void,void>::find( SharedAllocationRecord<void,void> * const arg_root , void * const arg_data_ptr )
{
  SharedAllocationRecord * root_next = 0 ;
  static constexpr SharedAllocationRecord * zero = nullptr;

  // Lock the list:
  while ( ( root_next = Kokkos::atomic_exchange( & arg_root->m_next , zero ) ) == nullptr );

  // Iterate searching for the record with this data pointer

  SharedAllocationRecord * r = root_next ;

  while ( ( r != arg_root ) && ( r->data() != arg_data_ptr ) ) { r = r->m_next ; }

  if ( r == arg_root ) { r = 0 ; }

  if ( nullptr != Kokkos::atomic_exchange( & arg_root->m_next , root_next ) ) {
    Kokkos::Impl::throw_runtime_exception("Kokkos::Impl::SharedAllocationRecord failed locking/unlocking");
  }
  return r ;
}
#else
SharedAllocationRecord<void,void> *
SharedAllocationRecord<void,void>::find( SharedAllocationRecord<void,void> * const , void * const )
{
  Kokkos::Impl::throw_runtime_exception("Kokkos::Impl::SharedAllocationRecord::find only works with KOKKOS_DEBUG enabled");
  return nullptr;
}
#endif


/**\brief  Construct and insert into 'arg_root' tracking set.
 *         use_count is zero.
 */
SharedAllocationRecord< void , void >::
SharedAllocationRecord(
#ifdef KOKKOS_DEBUG
                        SharedAllocationRecord<void,void> * arg_root,
#endif
                        SharedAllocationHeader            * arg_alloc_ptr
                      , size_t                              arg_alloc_size
                      , SharedAllocationRecord< void , void >::function_type  arg_dealloc
                      )
  : m_alloc_ptr(  arg_alloc_ptr )
  , m_alloc_size( arg_alloc_size )
  , m_dealloc(    arg_dealloc )
#ifdef KOKKOS_DEBUG
  , m_root( arg_root )
  , m_prev( 0 )
  , m_next( 0 )
#endif
  , m_count( 0 )
{
  if ( 0 != arg_alloc_ptr ) {

#ifdef KOKKOS_DEBUG
    // Insert into the root double-linked list for tracking
    //
    // before:  arg_root->m_next == next ; next->m_prev == arg_root
    // after:   arg_root->m_next == this ; this->m_prev == arg_root ;
    //              this->m_next == next ; next->m_prev == this

    m_prev = m_root ;
    static constexpr SharedAllocationRecord * zero = nullptr;

    // Read root->m_next and lock by setting to NULL
    while ( ( m_next = Kokkos::atomic_exchange( & m_root->m_next , zero ) ) == nullptr );

    m_next->m_prev = this ;

    // memory fence before completing insertion into linked list
    Kokkos::memory_fence();

    if ( nullptr != Kokkos::atomic_exchange( & m_root->m_next , this ) ) {
      Kokkos::Impl::throw_runtime_exception("Kokkos::Impl::SharedAllocationRecord failed locking/unlocking");
    }
#endif

  }
  else {
    Kokkos::Impl::throw_runtime_exception("Kokkos::Impl::SharedAllocationRecord given NULL allocation");
  }
}

void
SharedAllocationRecord< void , void >::
increment( SharedAllocationRecord< void , void > * arg_record )
{
  const int old_count = Kokkos::atomic_fetch_add( & arg_record->m_count , 1 );

  if ( old_count < 0 ) { // Error
    Kokkos::Impl::throw_runtime_exception("Kokkos::Impl::SharedAllocationRecord failed increment");
  }
}

SharedAllocationRecord< void , void > *
SharedAllocationRecord< void , void >::
decrement( SharedAllocationRecord< void , void > * arg_record )
{
  const int old_count = Kokkos::atomic_fetch_sub( & arg_record->m_count , 1 );

  if ( old_count == 1 ) {

    if (!Kokkos::is_initialized()) {
      std::stringstream ss;
      ss << "Kokkos allocation \"";
      ss << arg_record->get_label();
      ss << "\" is being deallocated after Kokkos::finalize was called\n";
      auto s = ss.str();
#ifdef KOKKOS_ENABLE_DEPRECATED_CODE
      std::cerr << s;
      std::cerr << "This behavior is incorrect Kokkos usage, and will crash in future releases\n";
#else
      Kokkos::Impl::throw_runtime_exception(s);
#endif
    }

#ifdef KOKKOS_DEBUG
    // before:  arg_record->m_prev->m_next == arg_record  &&
    //          arg_record->m_next->m_prev == arg_record
    //
    // after:   arg_record->m_prev->m_next == arg_record->m_next  &&
    //          arg_record->m_next->m_prev == arg_record->m_prev

    SharedAllocationRecord * root_next = 0 ;
    static constexpr SharedAllocationRecord * zero = nullptr;

    // Lock the list:
    while ( ( root_next = Kokkos::atomic_exchange( & arg_record->m_root->m_next , zero ) ) == nullptr );

    arg_record->m_next->m_prev = arg_record->m_prev ;

    if ( root_next != arg_record ) {
      arg_record->m_prev->m_next = arg_record->m_next ;
    }
    else {
      // before:  arg_record->m_root == arg_record->m_prev
      // after:   arg_record->m_root == arg_record->m_next
      root_next = arg_record->m_next ;
    }

    Kokkos::memory_fence();

    // Unlock the list:
    if ( nullptr != Kokkos::atomic_exchange( & arg_record->m_root->m_next , root_next ) ) {
      Kokkos::Impl::throw_runtime_exception("Kokkos::Impl::SharedAllocationRecord failed decrement unlocking");
    }

    arg_record->m_next = 0 ;
    arg_record->m_prev = 0 ;
#endif

    function_type d = arg_record->m_dealloc ;
    (*d)( arg_record );
    arg_record = 0 ;
  }
  else if ( old_count < 1 ) { // Error
    fprintf(stderr,"Kokkos::Impl::SharedAllocationRecord '%s' failed decrement count = %d\n", arg_record->m_alloc_ptr->m_label , old_count );
    fflush(stderr);
    Kokkos::Impl::throw_runtime_exception("Kokkos::Impl::SharedAllocationRecord failed decrement count");
  }

  return arg_record ;
}

#ifdef KOKKOS_DEBUG
void
SharedAllocationRecord< void , void >::
print_host_accessible_records( std::ostream & s
                             , const char * const space_name
                             , const SharedAllocationRecord * const root
                             , const bool detail )
{
  const SharedAllocationRecord< void , void > * r = root ;

  char buffer[256] ;

  if ( detail ) {
    do {
      //Formatting dependent on sizeof(uintptr_t)
      const char * format_string;

      if (sizeof(uintptr_t) == sizeof(unsigned long)) {
        format_string = "%s addr( 0x%.12lx ) list( 0x%.12lx 0x%.12lx ) extent[ 0x%.12lx + %.8ld ] count(%d) dealloc(0x%.12lx) %s\n";
      }
      else if (sizeof(uintptr_t) == sizeof(unsigned long long)) {
        format_string = "%s addr( 0x%.12llx ) list( 0x%.12llx 0x%.12llx ) extent[ 0x%.12llx + %.8ld ] count(%d) dealloc(0x%.12llx) %s\n";
      }

      snprintf( buffer , 256
              , format_string
              , space_name
              , reinterpret_cast<uintptr_t>( r )
              , reinterpret_cast<uintptr_t>( r->m_prev )
              , reinterpret_cast<uintptr_t>( r->m_next )
              , reinterpret_cast<uintptr_t>( r->m_alloc_ptr )
              , r->m_alloc_size
              , r->use_count()
              , reinterpret_cast<uintptr_t>( r->m_dealloc )
              , r->m_alloc_ptr->m_label
              );
      s << buffer ;
      r = r->m_next ;
    } while ( r != root );
  }
  else {
    do {
      if ( r->m_alloc_ptr ) {
        //Formatting dependent on sizeof(uintptr_t)
        const char * format_string;

        if (sizeof(uintptr_t) == sizeof(unsigned long)) {
          format_string = "%s [ 0x%.12lx + %ld ] %s\n";
        }
        else if (sizeof(uintptr_t) == sizeof(unsigned long long)) {
          format_string = "%s [ 0x%.12llx + %ld ] %s\n";
        }

        snprintf( buffer , 256
                , format_string
                , space_name
                , reinterpret_cast< uintptr_t >( r->data() )
                , r->size()
                , r->m_alloc_ptr->m_label
                );
      }
      else {
        snprintf( buffer , 256 , "%s [ 0 + 0 ]\n" , space_name );
      }
      s << buffer ;
      r = r->m_next ;
    } while ( r != root );
  }
}
#else
void
SharedAllocationRecord< void , void >::
print_host_accessible_records( std::ostream &
                             , const char * const
                             , const SharedAllocationRecord * const
                             , const bool )
{
  Kokkos::Impl::throw_runtime_exception(
      "Kokkos::Impl::SharedAllocationRecord::print_host_accessible_records"
      " only works with KOKKOS_DEBUG enabled");
}
#endif

} /* namespace Impl */
} /* namespace Kokkos */

