// Copyright (c) 2010 The Chromium Embedded Framework 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 "include/wrapper/cef_zip_archive.h"

#include <algorithm>
#include <memory>

#include "include/base/cef_logging.h"
#include "include/cef_stream.h"
#include "include/cef_zip_reader.h"
#include "include/wrapper/cef_byte_read_handler.h"

#if defined(OS_LINUX)
#include <wctype.h>
#endif

namespace {

// Convert |str| to lowercase in a Unicode-friendly manner.
CefString ToLower(const CefString& str) {
  std::wstring wstr = str;
  std::transform(wstr.begin(), wstr.end(), wstr.begin(), towlower);
  return wstr;
}

class CefZipFile : public CefZipArchive::File {
 public:
  CefZipFile() = default;

  CefZipFile(const CefZipFile&) = delete;
  CefZipFile& operator=(const CefZipFile&) = delete;

  bool Initialize(size_t data_size) {
    data_.reset(new (std::nothrow) unsigned char[data_size]);
    if (data_) {
      data_size_ = data_size;
      return true;
    } else {
      DLOG(ERROR) << "Failed to allocate " << data_size << " bytes of memory";
      data_size_ = 0;
      return false;
    }
  }

  const unsigned char* GetData() const override { return data_.get(); }

  size_t GetDataSize() const override { return data_size_; }

  CefRefPtr<CefStreamReader> GetStreamReader() const override {
    CefRefPtr<CefReadHandler> handler(new CefByteReadHandler(
        data_.get(), data_size_, const_cast<CefZipFile*>(this)));
    return CefStreamReader::CreateForHandler(handler);
  }

  unsigned char* data() { return data_.get(); }

 private:
  size_t data_size_ = 0;
  std::unique_ptr<unsigned char[]> data_;

  IMPLEMENT_REFCOUNTING(CefZipFile);
};

}  // namespace

// CefZipArchive implementation

CefZipArchive::CefZipArchive() = default;

CefZipArchive::~CefZipArchive() = default;

size_t CefZipArchive::Load(CefRefPtr<CefStreamReader> stream,
                           const CefString& password,
                           bool overwriteExisting) {
  base::AutoLock lock_scope(lock_);

  CefRefPtr<CefZipReader> reader(CefZipReader::Create(stream));
  if (!reader.get()) {
    return 0;
  }

  if (!reader->MoveToFirstFile()) {
    return 0;
  }

  FileMap::iterator it;
  size_t count = 0;

  do {
    const size_t size = static_cast<size_t>(reader->GetFileSize());
    if (size == 0) {
      // Skip directories and empty files.
      continue;
    }

    if (!reader->OpenFile(password)) {
      break;
    }

    const CefString& name = ToLower(reader->GetFileName());

    it = contents_.find(name);
    if (it != contents_.end()) {
      if (overwriteExisting) {
        contents_.erase(it);
      } else {  // Skip files that already exist.
        continue;
      }
    }

    CefRefPtr<CefZipFile> contents = new CefZipFile();
    if (!contents->Initialize(size)) {
      continue;
    }
    unsigned char* data = contents->data();
    size_t offset = 0;

    // Read the file contents.
    do {
      offset += reader->ReadFile(data + offset, size - offset);
    } while (offset < size && !reader->Eof());

    DCHECK(offset == size);

    reader->CloseFile();
    count++;

    // Add the file to the map.
    contents_.insert(std::make_pair(name, contents.get()));
  } while (reader->MoveToNextFile());

  return count;
}

void CefZipArchive::Clear() {
  base::AutoLock lock_scope(lock_);
  contents_.clear();
}

size_t CefZipArchive::GetFileCount() const {
  base::AutoLock lock_scope(lock_);
  return contents_.size();
}

bool CefZipArchive::HasFile(const CefString& fileName) const {
  base::AutoLock lock_scope(lock_);
  FileMap::const_iterator it = contents_.find(ToLower(fileName));
  return (it != contents_.end());
}

CefRefPtr<CefZipArchive::File> CefZipArchive::GetFile(
    const CefString& fileName) const {
  base::AutoLock lock_scope(lock_);
  FileMap::const_iterator it = contents_.find(ToLower(fileName));
  if (it != contents_.end()) {
    return it->second;
  }
  return nullptr;
}

bool CefZipArchive::RemoveFile(const CefString& fileName) {
  base::AutoLock lock_scope(lock_);
  FileMap::iterator it = contents_.find(ToLower(fileName));
  if (it != contents_.end()) {
    contents_.erase(it);
    return true;
  }
  return false;
}

size_t CefZipArchive::GetFiles(FileMap& map) const {
  base::AutoLock lock_scope(lock_);
  map = contents_;
  return contents_.size();
}
