// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ppapi/shared_impl/var_tracker.h"

#include <string.h>

#include <limits>

#include "base/logging.h"
#include "ppapi/shared_impl/id_assignment.h"
#include "ppapi/shared_impl/var.h"

namespace ppapi {

VarTracker::VarInfo::VarInfo()
    : var(),
      ref_count(0),
      track_with_no_reference_count(0) {
}

VarTracker::VarInfo::VarInfo(Var* v, int input_ref_count)
    : var(v),
      ref_count(input_ref_count),
      track_with_no_reference_count(0) {
}

VarTracker::VarTracker() : last_var_id_(0) {
}

VarTracker::~VarTracker() {
}

int32 VarTracker::AddVar(Var* var) {
  DCHECK(CalledOnValidThread());

  return AddVarInternal(var, ADD_VAR_TAKE_ONE_REFERENCE);
}

Var* VarTracker::GetVar(int32 var_id) const {
  DCHECK(CalledOnValidThread());

  VarMap::const_iterator result = live_vars_.find(var_id);
  if (result == live_vars_.end())
    return NULL;
  return result->second.var.get();
}

Var* VarTracker::GetVar(const PP_Var& var) const {
  DCHECK(CalledOnValidThread());

  if (!IsVarTypeRefcounted(var.type))
    return NULL;
  return GetVar(static_cast<int32>(var.value.as_id));
}

bool VarTracker::AddRefVar(int32 var_id) {
  DCHECK(CalledOnValidThread());

  DLOG_IF(ERROR, !CheckIdType(var_id, PP_ID_TYPE_VAR))
      << var_id << " is not a PP_Var ID.";
  VarMap::iterator found = live_vars_.find(var_id);
  if (found == live_vars_.end()) {
    NOTREACHED();  // Invalid var.
    return false;
  }

  VarInfo& info = found->second;
  if (info.ref_count == 0) {
    // All live vars with no refcount should be tracked objects.
    DCHECK(info.track_with_no_reference_count > 0);
    DCHECK(info.var->GetType() == PP_VARTYPE_OBJECT);

    TrackedObjectGettingOneRef(found);
  }

  // Basic refcount increment.
  info.ref_count++;
  return true;
}

bool VarTracker::AddRefVar(const PP_Var& var) {
  DCHECK(CalledOnValidThread());

  if (!IsVarTypeRefcounted(var.type))
    return false;
  return AddRefVar(static_cast<int32>(var.value.as_id));
}

bool VarTracker::ReleaseVar(int32 var_id) {
  DCHECK(CalledOnValidThread());

  DLOG_IF(ERROR, !CheckIdType(var_id, PP_ID_TYPE_VAR))
      << var_id << " is not a PP_Var ID.";
  VarMap::iterator found = live_vars_.find(var_id);
  if (found == live_vars_.end())
    return false;

  VarInfo& info = found->second;
  if (info.ref_count == 0) {
    NOTREACHED() << "Releasing an object with zero ref";
    return false;
  }
  info.ref_count--;

  if (info.ref_count == 0) {
    if (info.var->GetType() == PP_VARTYPE_OBJECT) {
      // Objects have special requirements and may not necessarily be released
      // when the refcount goes to 0.
      ObjectGettingZeroRef(found);
    } else {
      // All other var types can just be released.
      DCHECK(info.track_with_no_reference_count == 0);
      info.var->ResetVarID();
      live_vars_.erase(found);
    }
  }
  return true;
}

bool VarTracker::ReleaseVar(const PP_Var& var) {
  DCHECK(CalledOnValidThread());

  if (!IsVarTypeRefcounted(var.type))
    return false;
  return ReleaseVar(static_cast<int32>(var.value.as_id));
}

int32 VarTracker::AddVarInternal(Var* var, AddVarRefMode mode) {
  // If the plugin manages to create millions of strings.
  if (last_var_id_ == std::numeric_limits<int32>::max() >> kPPIdTypeBits)
    return 0;

  int32 new_id = MakeTypedId(++last_var_id_, PP_ID_TYPE_VAR);
  live_vars_.insert(std::make_pair(new_id,
      VarInfo(var, mode == ADD_VAR_TAKE_ONE_REFERENCE ? 1 : 0)));

  return new_id;
}

VarTracker::VarMap::iterator VarTracker::GetLiveVar(int32 id) {
  return live_vars_.find(id);
}

int VarTracker::GetRefCountForObject(const PP_Var& plugin_object) {
  DCHECK(CalledOnValidThread());

  VarMap::iterator found = GetLiveVar(plugin_object);
  if (found == live_vars_.end())
    return -1;
  return found->second.ref_count;
}

int VarTracker::GetTrackedWithNoReferenceCountForObject(
    const PP_Var& plugin_object) {
  DCHECK(CalledOnValidThread());

  VarMap::iterator found = GetLiveVar(plugin_object);
  if (found == live_vars_.end())
    return -1;
  return found->second.track_with_no_reference_count;
}

VarTracker::VarMap::iterator VarTracker::GetLiveVar(const PP_Var& var) {
  return live_vars_.find(static_cast<int32>(var.value.as_id));
}

VarTracker::VarMap::const_iterator VarTracker::GetLiveVar(
    const PP_Var& var) const {
  return live_vars_.find(static_cast<int32>(var.value.as_id));
}

bool VarTracker::IsVarTypeRefcounted(PP_VarType type) const {
  return type >= PP_VARTYPE_STRING;
}

PP_Var VarTracker::MakeArrayBufferPPVar(uint32 size_in_bytes) {
  DCHECK(CalledOnValidThread());

  scoped_refptr<ArrayBufferVar> array_buffer(CreateArrayBuffer(size_in_bytes));
  if (!array_buffer)
    return PP_MakeNull();
  return array_buffer->GetPPVar();
}

PP_Var VarTracker::MakeArrayBufferPPVar(uint32 size_in_bytes,
                                        const void* data) {
  DCHECK(CalledOnValidThread());

  scoped_refptr<ArrayBufferVar> array_buffer(CreateArrayBuffer(size_in_bytes));
  if (!array_buffer)
    return PP_MakeNull();
  memcpy(array_buffer->Map(), data, size_in_bytes);
  return array_buffer->GetPPVar();
}

std::vector<PP_Var> VarTracker::GetLiveVars() {
  DCHECK(CalledOnValidThread());

  std::vector<PP_Var> var_vector;
  var_vector.reserve(live_vars_.size());
  for (VarMap::const_iterator iter = live_vars_.begin();
       iter != live_vars_.end();
       ++iter) {
    var_vector.push_back(iter->second.var->GetPPVar());
  }
  return var_vector;
}

void VarTracker::TrackedObjectGettingOneRef(VarMap::const_iterator obj) {
  // Anybody using tracked objects should override this.
  NOTREACHED();
}

void VarTracker::ObjectGettingZeroRef(VarMap::iterator iter) {
  DeleteObjectInfoIfNecessary(iter);
}

bool VarTracker::DeleteObjectInfoIfNecessary(VarMap::iterator iter) {
  if (iter->second.ref_count != 0 ||
      iter->second.track_with_no_reference_count != 0)
    return false;  // Object still alive.
  iter->second.var->ResetVarID();
  live_vars_.erase(iter);
  return true;
}

}  // namespace ppapi