// Copyright 2013 The Flutter 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 "flutter/fml/icu_util.h"

#include <memory>
#include <mutex>

#include "flutter/fml/build_config.h"
#include "flutter/fml/logging.h"
#include "flutter/fml/mapping.h"
#include "flutter/fml/native_library.h"
#include "flutter/fml/paths.h"
#include "third_party/icu/source/common/unicode/udata.h"

namespace fml {
namespace icu {

class ICUContext {
 public:
  ICUContext(const std::string& icu_data_path) : valid_(false) {
    valid_ = SetupMapping(icu_data_path) && SetupICU();
  }

  ICUContext(std::unique_ptr<Mapping> mapping) : mapping_(std::move(mapping)) {
    valid_ = SetupICU();
  }

  ~ICUContext() = default;

  bool SetupMapping(const std::string& icu_data_path) {
    // Check if the path exists and it readable directly.
    auto fd =
        fml::OpenFile(icu_data_path.c_str(), false, fml::FilePermission::kRead);

    // Check the path relative to the current executable.
    if (!fd.is_valid()) {
      auto directory = fml::paths::GetExecutableDirectoryPath();

      if (!directory.first) {
        return false;
      }

      std::string path_relative_to_executable =
          paths::JoinPaths({directory.second, icu_data_path});

      fd = fml::OpenFile(path_relative_to_executable.c_str(), false,
                         fml::FilePermission::kRead);
    }

    if (!fd.is_valid()) {
      return false;
    }

    std::initializer_list<FileMapping::Protection> protection = {
        fml::FileMapping::Protection::kRead};

    auto file_mapping =
        std::make_unique<FileMapping>(fd, std::move(protection));

    if (file_mapping->GetSize() != 0) {
      mapping_ = std::move(file_mapping);
      return true;
    }

    return false;
  }

  bool SetupICU() {
    if (GetSize() == 0) {
      return false;
    }

    UErrorCode err_code = U_ZERO_ERROR;
    udata_setCommonData(GetMapping(), &err_code);
    return (err_code == U_ZERO_ERROR);
  }

  const uint8_t* GetMapping() const {
    return mapping_ ? mapping_->GetMapping() : nullptr;
  }

  size_t GetSize() const { return mapping_ ? mapping_->GetSize() : 0; }

  bool IsValid() const { return valid_; }

 private:
  bool valid_;
  std::unique_ptr<Mapping> mapping_;

  FML_DISALLOW_COPY_AND_ASSIGN(ICUContext);
};

void InitializeICUOnce(const std::string& icu_data_path) {
  static ICUContext* context = new ICUContext(icu_data_path);
  FML_CHECK(context->IsValid())
      << "Must be able to initialize the ICU context. Tried: " << icu_data_path;
}

std::once_flag g_icu_init_flag;
void InitializeICU(const std::string& icu_data_path) {
  std::call_once(g_icu_init_flag,
                 [&icu_data_path]() { InitializeICUOnce(icu_data_path); });
}

void InitializeICUFromMappingOnce(std::unique_ptr<Mapping> mapping) {
  static ICUContext* context = new ICUContext(std::move(mapping));
  FML_CHECK(context->IsValid())
      << "Unable to initialize the ICU context from a mapping.";
}

void InitializeICUFromMapping(std::unique_ptr<Mapping> mapping) {
  std::call_once(g_icu_init_flag, [mapping = std::move(mapping)]() mutable {
    InitializeICUFromMappingOnce(std::move(mapping));
  });
}

}  // namespace icu
}  // namespace fml