/** @file

  File system support classes.

  @section license License

  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

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "tscore/ink_memory.h"
#include "tscpp/util/TextView.h"

namespace ts
{
/** A file class for supporting path operations.
 */
class FilePath
{
  typedef FilePath self; ///< Self reference type.
public:
  FilePath();
  /// Construct from a null terminated string.
  explicit FilePath(char const *path);
  /// Construct from a string view.
  explicit FilePath(TextView const &path);
  /// Copy constructor - copies the path.
  FilePath(self const &that);
  /// Move constructor.
  FilePath(self &&that);
  /// Assign a new path.
  self &operator=(char const *path);
  /// Combine two paths, making sure there is exactly one separator between them.
  self operator/(self const &rhs);
  /// Create a new instance by appended @a path.
  self operator/(char const *path);
  /// Check if there is a path.
  bool has_path() const;
  /// Check if the path is absolute.
  bool is_absolute() const;
  /// Check if the path is not absolute.
  bool is_relative() const;
  /// Check if file is readable.
  bool is_readable() const;
  /// Access the path as a null terminated string.
  operator const char *() const;
  /// Access the path explicitly.
  char const *path() const;

  /// Return the file type value.
  int file_type() const;
  /// Size of the file or block device.
  off_t physical_size() const;

  bool is_char_device() const;
  bool is_block_device() const;
  bool is_dir() const;
  bool is_regular_file() const;

  // Utility methods.
  ats_scoped_fd open(int flags) const;

protected:
  /// Get the stat buffer.
  /// @return A valid stat buffer or @c nullptr if the system call failed.
  template <typename T> T stat(T (*f)(struct stat const *)) const;

  std::string _path; ///< File path.

  enum class STAT_P : int8_t { INVALID = -1, UNDEF = 0, VALID = 1 };
  mutable STAT_P _stat_p = STAT_P::UNDEF; ///< Whether _stat is valid.
  mutable struct stat _stat;              ///< File information.
};

/** A file support class for handling files as bulk content.

    @note This is used primarily for configuration files where the entire file is read every time
    and it's rarely (if ever) useful to read it incrementally. The general scheme is the entire file
    is read and then @c TextView elements are used to reference the bulk content.

    @internal The design goal of this class is to supplant the free functions later in this header.

 */
class BulkFile : public FilePath
{
  typedef BulkFile self;  ///< Self reference type.
  typedef FilePath super; ///< Parent type.
public:
  // Inherit super class constructors.
  using super::super;
  ///< Conversion constructor from base class.
  BulkFile(super &&that);
  /// Read the contents of the file in a local buffer.
  /// @return @c errno
  int load();
  TextView content() const;

private:
  std::string _content; ///< The file contents.
  size_t _len = -1;     ///< Length of file content.
};

/* ------------------------------------------------------------------- */

inline FilePath::FilePath()
{
  ink_zero(_stat);
}
inline FilePath::FilePath(char const *path) : _path(path)
{
  ink_zero(_stat);
}
inline FilePath::FilePath(TextView const &path) : _path(path.data(), path.size())
{
  ink_zero(_stat);
}
inline FilePath::FilePath(self const &that) : _path(that._path)
{
  ink_zero(_stat);
}
inline FilePath::FilePath(self &&that) : _path(std::move(that._path))
{
  ink_zero(_stat);
}
inline FilePath::operator const char *() const
{
  return _path.c_str();
}
inline char const *
FilePath::path() const
{
  return _path.c_str();
}

inline bool
FilePath::has_path() const
{
  return !_path.empty();
}
inline bool
FilePath::is_absolute() const
{
  return !_path.empty() && '/' == _path[0];
}
inline bool
FilePath::is_relative() const
{
  return !this->is_absolute();
}

template <typename T> T FilePath::stat(T (*f)(struct stat const *)) const
{
  if (STAT_P::UNDEF == _stat_p) {
    _stat_p = ::stat(_path.c_str(), &_stat) >= 0 ? STAT_P::VALID : STAT_P::INVALID;
  }
  return _stat_p == STAT_P::VALID ? f(&_stat) : T();
}

FilePath operator/(FilePath const &lhs, FilePath const &rhs);
FilePath operator/(char const *lhs, FilePath const &rhs);

inline int
FilePath::file_type() const
{
  return this->stat<int>([](struct stat const *s) -> int { return s->st_mode & S_IFMT; });
}

inline bool
FilePath::is_dir() const
{
  return this->file_type() == S_IFDIR;
}
inline bool
FilePath::is_char_device() const
{
  return this->file_type() == S_IFCHR;
}
inline bool
FilePath::is_block_device() const
{
  return this->file_type() == S_IFBLK;
}
inline bool
FilePath::is_regular_file() const
{
  return this->file_type() == S_IFREG;
}

inline off_t
FilePath::physical_size() const
{
  return this->stat<off_t>([](struct stat const *s) { return s->st_size; });
}

inline BulkFile::BulkFile(super &&that) : super(that) {}

/* ------------------------------------------------------------------- */
} // namespace ts
/* ------------------------------------------------------------------- */
