// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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.

#pragma once

#ifndef _WIN32
#define ARROW_HAVE_SIGACTION 1
#endif

#include <memory>
#include <string>
#include <utility>
#include <vector>

#if ARROW_HAVE_SIGACTION
#include <signal.h>  // Needed for struct sigaction
#endif

#include "arrow/io/interfaces.h"
#include "arrow/type_fwd.h"
#include "arrow/util/macros.h"
#include "arrow/util/windows_fixup.h"

namespace arrow {

class Buffer;

namespace io {

// Output stream that just writes to stdout.
class ARROW_EXPORT StdoutStream : public OutputStream {
 public:
  StdoutStream();
  ~StdoutStream() override {}

  Status Close() override;
  bool closed() const override;

  Result<int64_t> Tell() const override;

  Status Write(const void* data, int64_t nbytes) override;

 private:
  int64_t pos_;
};

// Output stream that just writes to stderr.
class ARROW_EXPORT StderrStream : public OutputStream {
 public:
  StderrStream();
  ~StderrStream() override {}

  Status Close() override;
  bool closed() const override;

  Result<int64_t> Tell() const override;

  Status Write(const void* data, int64_t nbytes) override;

 private:
  int64_t pos_;
};

// Input stream that just reads from stdin.
class ARROW_EXPORT StdinStream : public InputStream {
 public:
  StdinStream();
  ~StdinStream() override {}

  Status Close() override;
  bool closed() const override;

  Result<int64_t> Tell() const override;

  Result<int64_t> Read(int64_t nbytes, void* out) override;

  Result<std::shared_ptr<Buffer>> Read(int64_t nbytes) override;

 private:
  int64_t pos_;
};

}  // namespace io

namespace internal {

// NOTE: 8-bit path strings on Windows are encoded using UTF-8.
// Using MBCS would fail encoding some paths.

#if defined(_WIN32)
using NativePathString = std::wstring;
#else
using NativePathString = std::string;
#endif

class ARROW_EXPORT PlatformFilename {
 public:
  struct Impl;

  ~PlatformFilename();
  PlatformFilename();
  PlatformFilename(const PlatformFilename&);
  PlatformFilename(PlatformFilename&&);
  PlatformFilename& operator=(const PlatformFilename&);
  PlatformFilename& operator=(PlatformFilename&&);
  explicit PlatformFilename(const NativePathString& path);
  explicit PlatformFilename(const NativePathString::value_type* path);

  const NativePathString& ToNative() const;
  std::string ToString() const;

  PlatformFilename Parent() const;

  // These functions can fail for character encoding reasons.
  static Result<PlatformFilename> FromString(const std::string& file_name);
  Result<PlatformFilename> Join(const std::string& child_name) const;

  PlatformFilename Join(const PlatformFilename& child_name) const;

  bool operator==(const PlatformFilename& other) const;
  bool operator!=(const PlatformFilename& other) const;

  // Made public to avoid the proliferation of friend declarations.
  const Impl* impl() const { return impl_.get(); }

 private:
  std::unique_ptr<Impl> impl_;

  explicit PlatformFilename(Impl impl);
};

/// Create a directory if it doesn't exist.
///
/// Return whether the directory was created.
ARROW_EXPORT
Result<bool> CreateDir(const PlatformFilename& dir_path);

/// Create a directory and its parents if it doesn't exist.
///
/// Return whether the directory was created.
ARROW_EXPORT
Result<bool> CreateDirTree(const PlatformFilename& dir_path);

/// Delete a directory's contents (but not the directory itself) if it exists.
///
/// Return whether the directory existed.
ARROW_EXPORT
Result<bool> DeleteDirContents(const PlatformFilename& dir_path,
                               bool allow_not_found = true);

/// Delete a directory tree if it exists.
///
/// Return whether the directory existed.
ARROW_EXPORT
Result<bool> DeleteDirTree(const PlatformFilename& dir_path, bool allow_not_found = true);

// Non-recursively list the contents of the given directory.
// The returned names are the children's base names, not including dir_path.
ARROW_EXPORT
Result<std::vector<PlatformFilename>> ListDir(const PlatformFilename& dir_path);

/// Delete a file if it exists.
///
/// Return whether the file existed.
ARROW_EXPORT
Result<bool> DeleteFile(const PlatformFilename& file_path, bool allow_not_found = true);

/// Return whether a file exists.
ARROW_EXPORT
Result<bool> FileExists(const PlatformFilename& path);

/// Open a file for reading and return a file descriptor.
ARROW_EXPORT
Result<int> FileOpenReadable(const PlatformFilename& file_name);

/// Open a file for writing and return a file descriptor.
ARROW_EXPORT
Result<int> FileOpenWritable(const PlatformFilename& file_name, bool write_only = true,
                             bool truncate = true, bool append = false);

/// Read from current file position.  Return number of bytes read.
ARROW_EXPORT
Result<int64_t> FileRead(int fd, uint8_t* buffer, int64_t nbytes);
/// Read from given file position.  Return number of bytes read.
ARROW_EXPORT
Result<int64_t> FileReadAt(int fd, uint8_t* buffer, int64_t position, int64_t nbytes);

ARROW_EXPORT
Status FileWrite(int fd, const uint8_t* buffer, const int64_t nbytes);
ARROW_EXPORT
Status FileTruncate(int fd, const int64_t size);

ARROW_EXPORT
Status FileSeek(int fd, int64_t pos);
ARROW_EXPORT
Status FileSeek(int fd, int64_t pos, int whence);
ARROW_EXPORT
Result<int64_t> FileTell(int fd);
ARROW_EXPORT
Result<int64_t> FileGetSize(int fd);

ARROW_EXPORT
Status FileClose(int fd);

struct Pipe {
  int rfd;
  int wfd;
};

ARROW_EXPORT
Result<Pipe> CreatePipe();

ARROW_EXPORT
Status MemoryMapRemap(void* addr, size_t old_size, size_t new_size, int fildes,
                      void** new_addr);

ARROW_EXPORT
Result<std::string> GetEnvVar(const char* name);
ARROW_EXPORT
Result<std::string> GetEnvVar(const std::string& name);
ARROW_EXPORT
Result<NativePathString> GetEnvVarNative(const char* name);
ARROW_EXPORT
Result<NativePathString> GetEnvVarNative(const std::string& name);

ARROW_EXPORT
Status SetEnvVar(const char* name, const char* value);
ARROW_EXPORT
Status SetEnvVar(const std::string& name, const std::string& value);
ARROW_EXPORT
Status DelEnvVar(const char* name);
ARROW_EXPORT
Status DelEnvVar(const std::string& name);

ARROW_EXPORT
std::string ErrnoMessage(int errnum);
#if _WIN32
ARROW_EXPORT
std::string WinErrorMessage(int errnum);
#endif

ARROW_EXPORT
std::shared_ptr<StatusDetail> StatusDetailFromErrno(int errnum);
#if _WIN32
ARROW_EXPORT
std::shared_ptr<StatusDetail> StatusDetailFromWinError(int errnum);
#endif

template <typename... Args>
Status StatusFromErrno(int errnum, StatusCode code, Args&&... args) {
  return Status::FromDetailAndArgs(code, StatusDetailFromErrno(errnum),
                                   std::forward<Args>(args)...);
}

template <typename... Args>
Status IOErrorFromErrno(int errnum, Args&&... args) {
  return StatusFromErrno(errnum, StatusCode::IOError, std::forward<Args>(args)...);
}

#if _WIN32
template <typename... Args>
Status StatusFromWinError(int errnum, StatusCode code, Args&&... args) {
  return Status::FromDetailAndArgs(code, StatusDetailFromWinError(errnum),
                                   std::forward<Args>(args)...);
}

template <typename... Args>
Status IOErrorFromWinError(int errnum, Args&&... args) {
  return StatusFromWinError(errnum, StatusCode::IOError, std::forward<Args>(args)...);
}
#endif

ARROW_EXPORT
int ErrnoFromStatus(const Status&);

// Always returns 0 on non-Windows platforms (for Python).
ARROW_EXPORT
int WinErrorFromStatus(const Status&);

class ARROW_EXPORT TemporaryDir {
 public:
  ~TemporaryDir();

  /// '/'-terminated path to the temporary dir
  const PlatformFilename& path() { return path_; }

  /// Create a temporary subdirectory in the system temporary dir,
  /// named starting with `prefix`.
  static Result<std::unique_ptr<TemporaryDir>> Make(const std::string& prefix);

 private:
  PlatformFilename path_;

  explicit TemporaryDir(PlatformFilename&&);
};

class ARROW_EXPORT SignalHandler {
 public:
  typedef void (*Callback)(int);

  SignalHandler();
  explicit SignalHandler(Callback cb);
#if ARROW_HAVE_SIGACTION
  explicit SignalHandler(const struct sigaction& sa);
#endif

  Callback callback() const;
#if ARROW_HAVE_SIGACTION
  const struct sigaction& action() const;
#endif

 protected:
#if ARROW_HAVE_SIGACTION
  // Storing the full sigaction allows to restore the entire signal handling
  // configuration.
  struct sigaction sa_;
#else
  Callback cb_;
#endif
};

/// \brief Return the current handler for the given signal number.
ARROW_EXPORT
Result<SignalHandler> GetSignalHandler(int signum);
/// \brief Set a new handler for the given signal number.
///
/// The old signal handler is returned.
ARROW_EXPORT
Result<SignalHandler> SetSignalHandler(int signum, const SignalHandler& handler);

}  // namespace internal
}  // namespace arrow
