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

#import <Cocoa/Cocoa.h>

#include "base/file_util.h"
#include "base/logging.h"
#include "base/scoped_cftyperef.h"
#include "base/scoped_ptr.h"
#include "base/shared_memory.h"
#include "chrome/common/font_loader_mac.h"
#include "chrome/common/sandbox_mac_unittest_helper.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

using sandboxtest::MacSandboxTest;

bool CGFontFromFontContainer(ATSFontContainerRef container, CGFontRef* out) {
  // Count the number of fonts that were loaded.
  ItemCount fontCount = 0;
  OSStatus err = ATSFontFindFromContainer(container, kATSOptionFlagsDefault, 0,
                    NULL, &fontCount);

  if (err != noErr || fontCount < 1) {
    return false;
  }

  // Load font from container.
  ATSFontRef font_ref_ats = 0;
  ATSFontFindFromContainer(container, kATSOptionFlagsDefault, 1,
      &font_ref_ats, NULL);

  if (!font_ref_ats) {
    return false;
  }

  // Convert to cgFont.
  CGFontRef font_ref_cg = CGFontCreateWithPlatformFont(&font_ref_ats);

  if (!font_ref_cg) {
    return false;
  }

  *out = font_ref_cg;
  return true;
}

class ScopedFontContainer {
 public:
  explicit ScopedFontContainer(ATSFontContainerRef ref)
      : container_ref(ref) {}

  ~ScopedFontContainer() {
    ATSFontDeactivate(container_ref, NULL, kATSOptionFlagsDefault);
  }

  ATSFontContainerRef container_ref;
};

class FontLoadingTestCase : public sandboxtest::MacSandboxTestCase {
 public:
  FontLoadingTestCase() : font_data_length_(-1) {}
  virtual bool BeforeSandboxInit();
  virtual bool SandboxedTest();
 private:
  scoped_ptr<base::SharedMemory> font_shmem_;
  size_t font_data_length_;
};
REGISTER_SANDBOX_TEST_CASE(FontLoadingTestCase);


// Load raw font data into shared memory object.
bool FontLoadingTestCase::BeforeSandboxInit() {
  std::string font_data;
  if (!file_util::ReadFileToString(FilePath(test_data_.c_str()), &font_data)) {
    LOG(ERROR) << "Failed to read font data from file (" << test_data_ << ")";
    return false;
  }

  font_data_length_ = font_data.length();
  if (font_data_length_ <= 0) {
    LOG(ERROR) << "No font data: " << font_data_length_;
    return false;
  }

  font_shmem_.reset(new base::SharedMemory);
  if (!font_shmem_.get()) {
    LOG(ERROR) << "Failed to create shared memory object.";
    return false;
  }

  if (!font_shmem_->Create(L"", false, false, font_data_length_)) {
    LOG(ERROR) << "SharedMemory::Create failed";
    return false;
  }

  if (!font_shmem_->Map(font_data_length_)) {
    LOG(ERROR) << "SharedMemory::Map failed";
    return false;
  }

  memcpy(font_shmem_->memory(), font_data.c_str(), font_data_length_);
  if (!font_shmem_->Unmap())  {
    LOG(ERROR) << "SharedMemory::Unmap failed";
    return false;
  }
  return true;
}

bool FontLoadingTestCase::SandboxedTest() {
  base::SharedMemoryHandle shmem_handle;
  if (!font_shmem_->ShareToProcess(NULL, &shmem_handle)) {
    LOG(ERROR) << "SharedMemory::ShareToProcess failed";
    return false;
  }

  ATSFontContainerRef font_container;
  if (!FontLoader::ATSFontContainerFromBuffer(shmem_handle, font_data_length_,
          &font_container)) {
    LOG(ERROR) << "Call to CreateCGFontFromBuffer() failed";
    return false;
  }

  // Unload the font container when done.
  ScopedFontContainer scoped_unloader(font_container);

  CGFontRef font_ref;
  if (!CGFontFromFontContainer(font_container, &font_ref)) {
    LOG(ERROR) << "CGFontFromFontContainer failed";
    return false;
  }

  if (!font_ref) {
    LOG(ERROR) << "Got NULL CGFontRef";
    return false;
  }
  scoped_cftyperef<CGFontRef> cgfont;
  cgfont.reset(font_ref);

  const NSFont* nsfont = reinterpret_cast<const NSFont*>(
                         CTFontCreateWithGraphicsFont(cgfont.get(), 16.0,
                            NULL, NULL));
  if (!nsfont) {
    LOG(ERROR) << "CTFontCreateWithGraphicsFont() failed";
    return false;
  }

  // Do something with the font to make sure it's loaded.
  CGFloat cap_height = [nsfont capHeight];

  if (cap_height <= 0.0) {
    LOG(ERROR) << "Got bad value for [NSFont capHeight] " << cap_height;
    return false;
  }

  return true;
}

TEST_F(MacSandboxTest, FontLoadingTest) {
  FilePath temp_file_path;
  FILE* temp_file = file_util::CreateAndOpenTemporaryFile(&temp_file_path);
  ASSERT_TRUE(temp_file);
  file_util::ScopedFILE temp_file_closer(temp_file);

  base::SharedMemory font_data;
  uint32 font_data_size;
  NSFont* srcFont = [NSFont fontWithName:@"Geeza Pro" size:16.0];
  EXPECT_TRUE(FontLoader::LoadFontIntoBuffer(srcFont,
                  &font_data, &font_data_size));
  EXPECT_GT(font_data_size, 0U);

  file_util::WriteFileDescriptor(fileno(temp_file),
      static_cast<const char *>(font_data.memory()), font_data_size);

  ASSERT_TRUE(RunTestInSandbox(sandbox::SANDBOX_TYPE_RENDERER,
                  "FontLoadingTestCase", temp_file_path.value().c_str()));
  temp_file_closer.reset();
  ASSERT_TRUE(file_util::Delete(temp_file_path, false));
}

}  // namespace