// 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 <string>
#include <vector>

#include "base/strings/string_number_conversions.h"
#include "base/threading/platform_thread.h"
#include "ppapi/c/pp_var.h"
#include "ppapi/c/ppb_var.h"
#include "ppapi/proxy/ppapi_proxy_test.h"
#include "ppapi/shared_impl/ppb_var_shared.h"

namespace {
std::string VarToString(const PP_Var& var, const PPB_Var* ppb_var) {
  uint32_t len = 0;
  const char* utf8 = ppb_var->VarToUtf8(var, &len);
  return std::string(utf8, len);
}
const size_t kNumStrings = 100;
const size_t kNumThreads = 20;
const int kRefsToAdd = 20;
}  // namespace

namespace ppapi {
namespace proxy {

class PPB_VarTest : public PluginProxyTest {
 public:
  PPB_VarTest()
      : test_strings_(kNumStrings), vars_(kNumStrings),
        ppb_var_(ppapi::PPB_Var_Shared::GetVarInterface1_2()) {
    // Set the value of test_strings_[i] to "i".
    for (size_t i = 0; i < kNumStrings; ++i)
      test_strings_[i] = base::IntToString(i);
  }
 protected:
  std::vector<std::string> test_strings_;
  std::vector<PP_Var> vars_;
  const PPB_Var* ppb_var_;
};

// Test basic String operations.
TEST_F(PPB_VarTest, Strings) {
  for (size_t i = 0; i < kNumStrings; ++i) {
    vars_[i] = ppb_var_->VarFromUtf8(test_strings_[i].c_str(),
                                     test_strings_[i].length());
    EXPECT_EQ(test_strings_[i], VarToString(vars_[i], ppb_var_));
  }
  // At this point, they should each have a ref count of 1. Add some more.
  for (int ref = 0; ref < kRefsToAdd; ++ref) {
    for (size_t i = 0; i < kNumStrings; ++i) {
      ppb_var_->AddRef(vars_[i]);
      // Make sure the string is still there with the right value.
      EXPECT_EQ(test_strings_[i], VarToString(vars_[i], ppb_var_));
    }
  }
  for (int ref = 0; ref < kRefsToAdd; ++ref) {
    for (size_t i = 0; i < kNumStrings; ++i) {
      ppb_var_->Release(vars_[i]);
      // Make sure the string is still there with the right value.
      EXPECT_EQ(test_strings_[i], VarToString(vars_[i], ppb_var_));
    }
  }
  // Now remove the ref counts for each string and make sure they are gone.
  for (size_t i = 0; i < kNumStrings; ++i) {
    ppb_var_->Release(vars_[i]);
    uint32_t len = 10;
    const char* utf8 = ppb_var_->VarToUtf8(vars_[i], &len);
    EXPECT_EQ(NULL, utf8);
    EXPECT_EQ(0u, len);
  }
}

// PPB_VarTest.Threads tests string operations accessed by multiple threads.
namespace {
// These three delegate classes which precede the test are for use with
// PlatformThread. The test goes roughly like this:
// 1) Spawn kNumThreads 'CreateVar' threads, giving each a roughly equal subset
//    of test_strings_ to 'create'. Each 'CreateVar' thread also converts its
//    set of vars back in to strings so that the main test thread can verify
//    their values were correctly converted.
// 2) Spawn kNumThreads 'ChangeRefVar' threads. Each of these threads will
//    incremement & decrement the reference count of ALL vars kRefsToAdd times.
//    Finally, each thread adds 1 ref count. This leaves each var with a ref-
//    count of |kNumThreads + 1|. The main test thread removes a ref, leaving
//    each var with a ref-count of |kNumThreads|.
// 3) Spawn kNumThreads 'RemoveVar' threads. Each of these threads releases each
//    var once. Once all the threads have finished, there should be no vars
//    left.
class CreateVarThreadDelegate : public base::PlatformThread::Delegate {
 public:
  // |strings_in|, |vars|, and |strings_out| are arrays, and size is their size.
  // For each |strings_in[i]|, we will set |vars[i]| using that value. Then we
  // read the var back out to |strings_out[i]|.
  CreateVarThreadDelegate(PP_Module pp_module, const std::string* strings_in,
                          PP_Var* vars_out, std::string* strings_out,
                          size_t size)
      : pp_module_(pp_module), strings_in_(strings_in), vars_out_(vars_out),
        strings_out_(strings_out), size_(size) {
  }
  virtual ~CreateVarThreadDelegate() {}
  virtual void ThreadMain() {
    const PPB_Var* ppb_var = ppapi::PPB_Var_Shared::GetVarInterface1_2();
    for (size_t i = 0; i < size_; ++i) {
      vars_out_[i] = ppb_var->VarFromUtf8(strings_in_[i].c_str(),
                                          strings_in_[i].length());
      strings_out_[i] = VarToString(vars_out_[i], ppb_var);
    }
  }
 private:
  PP_Module pp_module_;
  const std::string* strings_in_;
  PP_Var* vars_out_;
  std::string* strings_out_;
  size_t size_;
};

// A thread that will increment and decrement the reference count of every var
// multiple times.
class ChangeRefVarThreadDelegate : public base::PlatformThread::Delegate {
 public:
  ChangeRefVarThreadDelegate(const std::vector<PP_Var>& vars) : vars_(vars) {
  }
  virtual ~ChangeRefVarThreadDelegate() {}
  virtual void ThreadMain() {
    const PPB_Var* ppb_var = ppapi::PPB_Var_Shared::GetVarInterface1_2();
    // Increment and decrement the reference count for each var kRefsToAdd
    // times. Note that we always AddRef once before doing the matching Release,
    // to ensure that we never accidentally release the last reference.
    for (int ref = 0; ref < kRefsToAdd; ++ref) {
      for (size_t i = 0; i < kNumStrings; ++i) {
        ppb_var->AddRef(vars_[i]);
        ppb_var->Release(vars_[i]);
      }
    }
    // Now add 1 ref to each Var. The net result is that all Vars will have a
    // ref-count of (kNumThreads + 1) after this. That will allow us to have all
    // threads release all vars later.
    for (size_t i = 0; i < kNumStrings; ++i) {
      ppb_var->AddRef(vars_[i]);
    }
  }
 private:
  std::vector<PP_Var> vars_;
};

// A thread that will decrement the reference count of every var once.
class RemoveRefVarThreadDelegate : public base::PlatformThread::Delegate {
 public:
  RemoveRefVarThreadDelegate(const std::vector<PP_Var>& vars) : vars_(vars) {
  }
  virtual ~RemoveRefVarThreadDelegate() {}
  virtual void ThreadMain() {
    const PPB_Var* ppb_var = ppapi::PPB_Var_Shared::GetVarInterface1_2();
    for (size_t i = 0; i < kNumStrings; ++i) {
      ppb_var->Release(vars_[i]);
    }
  }
 private:
  std::vector<PP_Var> vars_;
};

}  // namespace

TEST_F(PPB_VarTest, Threads) {
  std::vector<base::PlatformThreadHandle> create_var_threads(kNumThreads);
  std::vector<CreateVarThreadDelegate> create_var_delegates;
  // The strings that the threads will re-extract from Vars (so we can check
  // that they match the original strings).
  std::vector<std::string> strings_out(kNumStrings);
  size_t strings_per_thread = kNumStrings/kNumThreads;
  // Give each thread an equal slice of strings to turn in to vars. (Except the
  // last thread may get fewer if kNumStrings is not evenly divisible by
  // kNumThreads).
  for (size_t slice_start= 0; slice_start < kNumStrings;
       slice_start += strings_per_thread) {
    create_var_delegates.push_back(
        CreateVarThreadDelegate(pp_module(),
                                &test_strings_[slice_start],
                                &vars_[slice_start],
                                &strings_out[slice_start],
                                std::min(strings_per_thread,
                                         kNumStrings - slice_start)));
  }
  // Now run then join all the threads.
  for (size_t i = 0; i < kNumThreads; ++i)
    base::PlatformThread::Create(0, &create_var_delegates[i],
                                 &create_var_threads[i]);
  for (size_t i = 0; i < kNumThreads; ++i)
    base::PlatformThread::Join(create_var_threads[i]);
  // Now check that the strings have the expected values.
  EXPECT_EQ(test_strings_, strings_out);

  // Tinker with the reference counts in a multithreaded way.
  std::vector<base::PlatformThreadHandle> change_ref_var_threads(kNumThreads);
  std::vector<ChangeRefVarThreadDelegate> change_ref_var_delegates;
  for (size_t i = 0; i < kNumThreads; ++i)
    change_ref_var_delegates.push_back(ChangeRefVarThreadDelegate(vars_));
  for (size_t i = 0; i < kNumThreads; ++i) {
    base::PlatformThread::Create(0, &change_ref_var_delegates[i],
                                 &change_ref_var_threads[i]);
  }
  for (size_t i = 0; i < kNumThreads; ++i)
    base::PlatformThread::Join(change_ref_var_threads[i]);

  // Now each var has a refcount of (kNumThreads + 1). Let's decrement each var
  // once so that every 'RemoveRef' thread (spawned below) owns 1 reference, and
  // when the last one removes a ref, the Var will be deleted.
  for (size_t i = 0; i < kNumStrings; ++i) {
    ppb_var_->Release(vars_[i]);
  }

  // Check that all vars are still valid and have the values we expect.
  for (size_t i = 0; i < kNumStrings; ++i)
    EXPECT_EQ(test_strings_[i], VarToString(vars_[i], ppb_var_));

  // Remove the last reference counts for all vars.
  std::vector<base::PlatformThreadHandle> remove_ref_var_threads(kNumThreads);
  std::vector<RemoveRefVarThreadDelegate> remove_ref_var_delegates;
  for (size_t i = 0; i < kNumThreads; ++i)
    remove_ref_var_delegates.push_back(RemoveRefVarThreadDelegate(vars_));
  for (size_t i = 0; i < kNumThreads; ++i) {
    base::PlatformThread::Create(0, &remove_ref_var_delegates[i],
                                 &remove_ref_var_threads[i]);
  }
  for (size_t i = 0; i < kNumThreads; ++i)
    base::PlatformThread::Join(remove_ref_var_threads[i]);

  // All the vars should no longer represent valid strings.
  for (size_t i = 0; i < kNumStrings; ++i) {
    uint32_t len = 10;
    const char* utf8 = ppb_var_->VarToUtf8(vars_[i], &len);
    EXPECT_EQ(NULL, utf8);
    EXPECT_EQ(0u, len);
  }
}

}  // namespace proxy
}  // namespace ppapi
