// Copyright (c) the JPEG XL Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#if defined(_WIN32) || defined(_WIN64)
#include "third_party/dirent.h"

#include "lib/base/status.h"

#ifndef NOMINMAX
#define NOMINMAX
#endif  // NOMINMAX

#include <errno.h>
#include <windows.h>

#include <memory>
#include <string>

int mkdir(const char* path, mode_t /*mode*/) {
  const LPSECURITY_ATTRIBUTES sec = nullptr;
  if (!CreateDirectory(path, sec)) {
    JXL_NOTIFY_ERROR("Failed to create directory %s", path);
    return -1;
  }
  return 0;
}

// Modified from code bearing the following notice:
// https://trac.wildfiregames.com/browser/ps/trunk/source/lib/sysdep/os/
/* Copyright (C) 2010 Wildfire Games.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

struct DIR {
  HANDLE hFind;

  WIN32_FIND_DATA findData;  // indeterminate if hFind == INVALID_HANDLE_VALUE

  // readdir will return the address of this member.
  // (must be stored in DIR to allow multiple independent
  // opendir/readdir sequences).
  dirent ent;

  // used by readdir to skip the first FindNextFile.
  size_t numCalls = 0;
};

static bool IsValidDirectory(const char* path) {
  const DWORD fileAttributes = GetFileAttributes(path);

  // path not found
  if (fileAttributes == INVALID_FILE_ATTRIBUTES) return false;

  // not a directory
  if ((fileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) return false;

  return true;
}

DIR* opendir(const char* path) {
  if (!IsValidDirectory(path)) {
    errno = ENOENT;
    return nullptr;
  }

  std::unique_ptr<DIR> d(new DIR);

  // NB: "c:\\path" only returns information about that directory;
  // trailing slashes aren't allowed. append "\\*" to retrieve its entries.
  std::string searchPath(path);
  if (searchPath.back() != '/' && searchPath.back() != '\\') {
    searchPath += '\\';
  }
  searchPath += '*';

  // (we don't defer FindFirstFile until readdir because callers
  // expect us to return 0 if directory reading will/did fail.)
  d->hFind = FindFirstFile(searchPath.c_str(), &d->findData);
  if (d->hFind != INVALID_HANDLE_VALUE) return d.release();
  if (GetLastError() == ERROR_NO_MORE_FILES) return d.release();  // empty

  JXL_NOTIFY_ERROR("Failed to open directory %s", searchPath.c_str());
  return nullptr;
}

int closedir(DIR* dir) {
  delete dir;
  return 0;
}

dirent* readdir(DIR* d) {
  // "empty" case from opendir
  if (d->hFind == INVALID_HANDLE_VALUE) return nullptr;

  // until end of directory or a valid entry was found:
  for (;;) {
    if (d->numCalls++ != 0)  // (skip first call to FindNextFile - see opendir)
    {
      if (!FindNextFile(d->hFind, &d->findData)) {
        JXL_DASSERT(GetLastError() == ERROR_NO_MORE_FILES);
        SetLastError(0);
        return nullptr;  // end of directory or error
      }
    }

    // only return non-hidden and non-system entries
    if ((d->findData.dwFileAttributes &
         (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) == 0) {
      d->ent.d_name = d->findData.cFileName;
      return &d->ent;
    }
  }
}

#endif  // #if defined(_WIN32) || defined(_WIN64)
