/* vim:set ts=2 sw=2 sts=2 et: */
/**
 * \author     Marcus Holland-Moritz (github@mhxnet.de)
 * \copyright  Copyright (c) Marcus Holland-Moritz
 *
 * This file is part of dwarfs.
 *
 * 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.
 *
 * SPDX-License-Identifier: MIT
 */

#pragma once

#include <cstdint>
#include <filesystem>
#include <memory>
#include <type_traits>

#include <dwarfs/error.h>
#include <dwarfs/file_type.h>

namespace dwarfs {

class file_stat {
 public:
  using valid_fields_type = uint32_t;
  using perms_type = std::underlying_type_t<std::filesystem::perms>;
  using mode_type = uint32_t;
  using dev_type = uint64_t;
  using ino_type = uint64_t;
  using nlink_type = uint64_t;
  using uid_type = uint32_t;
  using gid_type = uint32_t;
  using off_type = int64_t;
  using blksize_type = int64_t;
  using blkcnt_type = int64_t;
  using time_type = int64_t;

  static constexpr valid_fields_type dev_valid = 1 << 0;
  static constexpr valid_fields_type ino_valid = 1 << 1;
  static constexpr valid_fields_type nlink_valid = 1 << 2;
  static constexpr valid_fields_type mode_valid = 1 << 3;
  static constexpr valid_fields_type uid_valid = 1 << 4;
  static constexpr valid_fields_type gid_valid = 1 << 5;
  static constexpr valid_fields_type rdev_valid = 1 << 6;
  static constexpr valid_fields_type size_valid = 1 << 7;
  static constexpr valid_fields_type blksize_valid = 1 << 8;
  static constexpr valid_fields_type blocks_valid = 1 << 9;
  static constexpr valid_fields_type atime_valid = 1 << 10;
  static constexpr valid_fields_type mtime_valid = 1 << 11;
  static constexpr valid_fields_type ctime_valid = 1 << 12;
  static constexpr valid_fields_type all_valid = (1 << 13) - 1;

  file_stat();
  explicit file_stat(std::filesystem::path const& path);

  void ensure_valid(valid_fields_type fields) const;

  std::filesystem::file_status status() const;
  posix_file_type::value type() const;

  perms_type permissions() const;
  void set_permissions(perms_type perms);

  dev_type dev() const;
  dev_type dev_unchecked() const { return dev_; }
  void set_dev(dev_type dev);

  ino_type ino() const;
  ino_type ino_unchecked() const { return ino_; }
  void set_ino(ino_type ino);

  nlink_type nlink() const;
  nlink_type nlink_unchecked() const { return nlink_; }
  void set_nlink(nlink_type nlink);

  mode_type mode() const;
  mode_type mode_unchecked() const { return mode_; }
  void set_mode(mode_type mode);

  uid_type uid() const;
  uid_type uid_unchecked() const { return uid_; }
  void set_uid(uid_type uid);

  gid_type gid() const;
  gid_type gid_unchecked() const { return gid_; }
  void set_gid(gid_type gid);

  dev_type rdev() const;
  dev_type rdev_unchecked() const { return rdev_; }
  void set_rdev(dev_type rdev);

  off_type size() const;
  off_type size_unchecked() const { return size_; }
  void set_size(off_type size);

  blksize_type blksize() const;
  blksize_type blksize_unchecked() const { return blksize_; }
  void set_blksize(blksize_type blksize);

  blkcnt_type blocks() const;
  blkcnt_type blocks_unchecked() const { return blocks_; }
  void set_blocks(blkcnt_type blocks);

  time_type atime() const;
  time_type atime_unchecked() const { return atime_; }
  void set_atime(time_type atime);

  time_type mtime() const;
  time_type mtime_unchecked() const { return mtime_; }
  void set_mtime(time_type mtime);

  time_type ctime() const;
  time_type ctime_unchecked() const { return ctime_; }
  void set_ctime(time_type ctime);

  bool is_directory() const;
  bool is_regular_file() const;
  bool is_symlink() const;
  bool is_device() const;

  static std::string perm_string(mode_type mode);
  static std::string mode_string(mode_type mode);

  std::string perm_string() const { return perm_string(mode()); }
  std::string mode_string() const { return mode_string(mode()); }

  template <typename T>
  void copy_to(T* out) const {
    copy_to_impl<true>(out);
  }

  template <typename T>
  void copy_to_without_block_info(T* out) const {
    copy_to_impl<false>(out);
  }

 private:
  template <bool with_block_info = true, typename T>
  void copy_to_impl(T* out) const {
    constexpr valid_fields_type required_fields{
        with_block_info ? all_valid
                        : all_valid & ~(blksize_valid | blocks_valid)};
    ensure_valid(required_fields);
    out->st_dev = dev_;
    out->st_ino = ino_;
    out->st_nlink = nlink_;
    out->st_mode = mode_;
    out->st_uid = uid_;
    out->st_gid = gid_;
    out->st_rdev = rdev_;
    out->st_size = size_;
    if constexpr (with_block_info) {
      out->st_blksize = blksize_;
      out->st_blocks = blocks_;
    }
    out->st_atime = atime_;
    out->st_mtime = mtime_;
    out->st_ctime = ctime_;
  }

  uint32_t valid_fields_{0};
  dev_type dev_{};
  ino_type ino_{};
  nlink_type nlink_{};
  mode_type mode_{};
  uid_type uid_{};
  gid_type gid_{};
  dev_type rdev_{};
  off_type size_{};
  blksize_type blksize_{};
  blkcnt_type blocks_{};
  time_type atime_{};
  time_type mtime_{};
  time_type ctime_{};
  std::exception_ptr exception_;
};

} // namespace dwarfs
