use std::fs::{File, OpenOptions};
use std::io::{Error, Result};
use std::mem;
use std::os::windows::ffi::OsStrExt;
use std::os::windows::fs::OpenOptionsExt;
use std::os::windows::io::{AsRawHandle, FromRawHandle};
use std::path::Path;
use std::ptr;

use super::FsStats;
use winapi::shared::minwindef::{BOOL, DWORD};
use winapi::shared::winerror::ERROR_LOCK_VIOLATION;
use winapi::um::fileapi::{GetDiskFreeSpaceW, FILE_ALLOCATION_INFO, FILE_STANDARD_INFO};
use winapi::um::fileapi::{GetVolumePathNameW, LockFileEx, SetFileInformationByHandle, UnlockFile};
use winapi::um::handleapi::DuplicateHandle;
use winapi::um::minwinbase::{FileAllocationInfo, FileStandardInfo};
use winapi::um::minwinbase::{LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY};
use winapi::um::processthreadsapi::GetCurrentProcess;
use winapi::um::winbase::{
  GetFileInformationByHandleEx, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_DELETE_ON_CLOSE,
  FILE_FLAG_FIRST_PIPE_INSTANCE, FILE_FLAG_NO_BUFFERING, FILE_FLAG_OPEN_NO_RECALL,
  FILE_FLAG_OPEN_REPARSE_POINT, FILE_FLAG_OPEN_REQUIRING_OPLOCK, FILE_FLAG_OVERLAPPED,
  FILE_FLAG_POSIX_SEMANTICS, FILE_FLAG_RANDOM_ACCESS, FILE_FLAG_SEQUENTIAL_SCAN,
  FILE_FLAG_SESSION_AWARE, FILE_FLAG_WRITE_THROUGH, SECURITY_IDENTIFICATION,
};
use winapi::um::winnt::{
  DUPLICATE_SAME_ACCESS, FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_COMPRESSED, FILE_ATTRIBUTE_DEVICE,
  FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_ENCRYPTED, FILE_ATTRIBUTE_HIDDEN,
  FILE_ATTRIBUTE_INTEGRITY_STREAM, FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
  FILE_ATTRIBUTE_NO_SCRUB_DATA, FILE_ATTRIBUTE_OFFLINE, FILE_ATTRIBUTE_PINNED,
  FILE_ATTRIBUTE_READONLY, FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS, FILE_ATTRIBUTE_RECALL_ON_OPEN,
  FILE_ATTRIBUTE_REPARSE_POINT, FILE_ATTRIBUTE_SPARSE_FILE, FILE_ATTRIBUTE_SYSTEM,
  FILE_ATTRIBUTE_TEMPORARY, FILE_ATTRIBUTE_UNPINNED, FILE_ATTRIBUTE_VIRTUAL, FILE_SHARE_DELETE,
  FILE_SHARE_READ, FILE_SHARE_WRITE,
};

/// 文件属性
#[repr(u32)]
#[derive(Debug, Clone)]
pub enum FileAttribute {
  /// 隐藏文件
  Hidden = FILE_ATTRIBUTE_HIDDEN,
  /// 压缩
  Compressed = FILE_ATTRIBUTE_COMPRESSED,
  /// 档案文件
  Archive = FILE_ATTRIBUTE_ARCHIVE,
  /// 设备
  Device = FILE_ATTRIBUTE_DEVICE,
  /// 目录
  Directory = FILE_ATTRIBUTE_DIRECTORY,
  /// 解析文件
  SparseFile = FILE_ATTRIBUTE_SPARSE_FILE,
  /// 离线
  Offline = FILE_ATTRIBUTE_OFFLINE,
  // /// EA
  // Ea = FILE_ATTRIBUTE_EA,
  /// 加密
  Encrypted = FILE_ATTRIBUTE_ENCRYPTED,
  /// 完整性流
  IntegrityStream = FILE_ATTRIBUTE_INTEGRITY_STREAM,
  /// 正常
  Normal = FILE_ATTRIBUTE_NORMAL,
  /// 不内容索引
  NotContentIndexed = FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
  /// 不净化数据
  NoScrubData = FILE_ATTRIBUTE_NO_SCRUB_DATA,
  /// 固定
  Pinned = FILE_ATTRIBUTE_PINNED,
  /// 只读
  Readonly = FILE_ATTRIBUTE_READONLY,
  /// 调用数据访问
  RecallOnDataAccess = FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS,
  /// 调用打开
  RecallOnOpen = FILE_ATTRIBUTE_RECALL_ON_OPEN,
  /// 解析点
  ReparsePoint = FILE_ATTRIBUTE_REPARSE_POINT,
  /// 系统
  System = FILE_ATTRIBUTE_SYSTEM,
  /// 临时
  Temporary = FILE_ATTRIBUTE_TEMPORARY,
  /// 不固定
  UnPinned = FILE_ATTRIBUTE_UNPINNED,
  /// 虚拟
  Virtual = FILE_ATTRIBUTE_VIRTUAL,
  /// 无权限
  Empty = 0,
}
/// 共享
#[repr(u32)]
#[derive(Debug, Clone)]
pub enum FileShare {
  /// 只读
  Read = FILE_SHARE_READ,
  /// 只写
  Write = FILE_SHARE_WRITE,
  /// 只删
  Delete = FILE_SHARE_DELETE,
  /// 读写
  ReadWrite = FILE_SHARE_READ | FILE_SHARE_WRITE,
  /// 所有
  All = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
  /// 无权限
  Empty = 0,
}
/// This will override the read, write, and append flags on the OpenOptions structure. This method provides fine-grained control over the permissions to read, write and append data, attributes (like hidden and system), and extended attributes.
#[repr(u32)]
#[derive(Debug, Clone)]
pub enum FileAccess {
  /// 无权限
  Empty = 0,
}

/// 安全
#[repr(u32)]
#[derive(Debug, Clone)]
pub enum FileSecurity {
  /// 标识
  Identification = SECURITY_IDENTIFICATION,
  /// 无权限
  Empty = 0,
}
/// 安全
#[repr(u32)]
#[derive(Debug, Clone)]
pub enum FileCustom {
  /// 直接写入
  WriteThrough = FILE_FLAG_WRITE_THROUGH,
  /// 感知会话
  SessionAware = FILE_FLAG_SESSION_AWARE,
  /// 序列扫描
  SequentialScan = FILE_FLAG_SEQUENTIAL_SCAN,
  /// 随机接入
  RandomAccess = FILE_FLAG_RANDOM_ACCESS,
  /// 可移植性
  PosixSeantics = FILE_FLAG_POSIX_SEMANTICS,
  /// 重叠
  Overlapped = FILE_FLAG_OVERLAPPED,
  /// 打开需要打开锁
  OpenRequiringOplock = FILE_FLAG_OPEN_REQUIRING_OPLOCK,
  /// 打开重新分析点
  OpenReparsePoint = FILE_FLAG_OPEN_REPARSE_POINT,
  /// 调用时打开
  OpenOnRecall = FILE_FLAG_OPEN_NO_RECALL,
  /// 没缓存
  NoBuffering = FILE_FLAG_NO_BUFFERING,
  /// 第一个管道
  FirstPipeInstance = FILE_FLAG_FIRST_PIPE_INSTANCE,
  /// 关闭时删除
  DeleteOnClose = FILE_FLAG_DELETE_ON_CLOSE,
  /// 备份语义
  BackupSemantics = FILE_FLAG_BACKUP_SEMANTICS,
  /// 无权限
  Empty = 0,
}
/// 锁共享
pub(crate) fn options_lock_share(option: &mut OpenOptions, mode: FileShare) -> &mut OpenOptions {
  option.share_mode(mode as u32)
}
/// 锁接入
pub(crate) fn options_lock_access(
  option: &mut OpenOptions,
  access: FileAccess,
) -> &mut OpenOptions {
  option.access_mode(access as u32)
}
/// 文件属性
pub(crate) fn options_attributes(
  option: &mut OpenOptions,
  attributes: impl IntoIterator<Item = FileAttribute>,
) -> &mut OpenOptions {
  let mut sum = 0;
  for v in attributes {
    sum = sum | v as u32;
  }
  option.attributes(sum)
}
/// 文件属性
pub(crate) fn options_security_qos_flags(
  option: &mut OpenOptions,
  flags: impl IntoIterator<Item = FileSecurity>,
) -> &mut OpenOptions {
  let mut sum = 0;
  for v in flags {
    sum = sum | v as u32;
  }
  option.security_qos_flags(sum)
}
/// 自定义标识
/// Custom flags can only set flags, not remove flags set by Rust's options. This option overwrites any previously set custom flags.
pub(crate) fn options_custom_flags(
  option: &mut OpenOptions,
  flags: impl IntoIterator<Item = FileCustom>,
) -> &mut OpenOptions {
  let mut sum = 0;
  for v in flags {
    sum = sum | v as u32;
  }
  option.custom_flags(sum)
}
pub(crate) fn duplicate(file: &File) -> Result<File> {
  unsafe {
    let mut handle = ptr::null_mut();
    let current_process = GetCurrentProcess();
    let ret = DuplicateHandle(
      current_process,
      file.as_raw_handle(),
      current_process,
      &mut handle,
      0,
      true as BOOL,
      DUPLICATE_SAME_ACCESS,
    );
    if ret == 0 {
      Err(Error::last_os_error())
    } else {
      Ok(File::from_raw_handle(handle))
    }
  }
}
///
pub(crate) fn allocated_size(file: &File) -> Result<u64> {
  unsafe {
    let mut info: FILE_STANDARD_INFO = mem::zeroed();

    let ret = GetFileInformationByHandleEx(
      file.as_raw_handle(),
      FileStandardInfo,
      &mut info as *mut _ as *mut _,
      mem::size_of::<FILE_STANDARD_INFO>() as DWORD,
    );

    if ret == 0 {
      Err(Error::last_os_error())
    } else {
      Ok(*info.AllocationSize.QuadPart() as u64)
    }
  }
}
///
pub(crate) fn allocate(file: &File, len: u64) -> Result<()> {
  if allocated_size(file)? < len {
    unsafe {
      let mut info: FILE_ALLOCATION_INFO = mem::zeroed();
      *info.AllocationSize.QuadPart_mut() = len as i64;
      let ret = SetFileInformationByHandle(
        file.as_raw_handle(),
        FileAllocationInfo,
        &mut info as *mut _ as *mut _,
        mem::size_of::<FILE_ALLOCATION_INFO>() as DWORD,
      );
      if ret == 0 {
        return Err(Error::last_os_error());
      }
    }
  }
  if file.metadata()?.len() < len {
    file.set_len(len)
  } else {
    Ok(())
  }
}
///
pub(crate) fn lock_shared(file: &File) -> Result<()> {
  lock_file(file, 0)
}
///
pub(crate) fn lock_exclusive(file: &File) -> Result<()> {
  lock_file(file, LOCKFILE_EXCLUSIVE_LOCK)
}
///
pub(crate) fn try_lock_shared(file: &File) -> Result<()> {
  lock_file(file, LOCKFILE_FAIL_IMMEDIATELY)
}
///
pub(crate) fn try_lock_exclusive(file: &File) -> Result<()> {
  lock_file(file, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY)
}
///
pub(crate) fn unlock(file: &File) -> Result<()> {
  unsafe {
    let ret = UnlockFile(file.as_raw_handle(), 0, 0, !0, !0);
    if ret == 0 {
      Err(Error::last_os_error())
    } else {
      Ok(())
    }
  }
}
///
pub(crate) fn lock_error() -> Error {
  Error::from_raw_os_error(ERROR_LOCK_VIOLATION as i32)
}
///
fn lock_file(file: &File, flags: DWORD) -> Result<()> {
  unsafe {
    let mut overlapped = mem::zeroed();
    let ret = LockFileEx(file.as_raw_handle(), flags, 0, !0, !0, &mut overlapped);
    if ret == 0 {
      Err(Error::last_os_error())
    } else {
      Ok(())
    }
  }
}
///
fn volume_path(path: &Path, volume_path: &mut [u16]) -> Result<()> {
  let path_utf8: Vec<u16> = path.as_os_str().encode_wide().chain(Some(0)).collect();
  unsafe {
    let ret = GetVolumePathNameW(
      path_utf8.as_ptr(),
      volume_path.as_mut_ptr(),
      volume_path.len() as DWORD,
    );
    if ret == 0 {
      Err(Error::last_os_error())
    } else {
      Ok(())
    }
  }
}
///
pub(crate) fn statvfs(path: &Path) -> Result<FsStats> {
  let root_path: &mut [u16] = &mut [0; 261];
  volume_path(path, root_path)?;
  unsafe {
    let mut sectors_per_cluster = 0;
    let mut bytes_per_sector = 0;
    let mut number_of_free_clusters = 0;
    let mut total_number_of_clusters = 0;
    let ret = GetDiskFreeSpaceW(
      root_path.as_ptr(),
      &mut sectors_per_cluster,
      &mut bytes_per_sector,
      &mut number_of_free_clusters,
      &mut total_number_of_clusters,
    );
    if ret == 0 {
      Err(Error::last_os_error())
    } else {
      let bytes_per_cluster = sectors_per_cluster as u64 * bytes_per_sector as u64;
      let free_space = bytes_per_cluster * number_of_free_clusters as u64;
      let total_space = bytes_per_cluster * total_number_of_clusters as u64;
      Ok(FsStats {
        free_space: free_space,
        available_space: free_space,
        total_space: total_space,
        allocation_granularity: bytes_per_cluster,
      })
    }
  }
}

#[cfg(test)]
mod test {
  use std::fs;
  use std::os::windows::io::AsRawHandle;

  use crate::fs::{lock_contended_error, FileExt};

  /// The duplicate method returns a file with a new file handle.
  #[test]
  fn duplicate_new_handle() {
    let tempdir = tempdir::TempDir::new("fs2").unwrap();
    let path = tempdir.path().join("fs2");
    let file1 = fs::OpenOptions::new()
      .write(true)
      .create(true)
      .open(&path)
      .unwrap();
    let file2 = file1.duplicate().unwrap();
    assert!(file1.as_raw_handle() != file2.as_raw_handle());
  }

  /// A duplicated file handle does not have access to the original handle's locks.
  #[test]
  fn lock_duplicate_handle_independence() {
    let tempdir = tempdir::TempDir::new("fs2").unwrap();
    let path = tempdir.path().join("fs2");
    let file1 = fs::OpenOptions::new()
      .read(true)
      .write(true)
      .create(true)
      .open(&path)
      .unwrap();
    let file2 = file1.duplicate().unwrap();

    // Locking the original file handle will block the duplicate file handle from opening a lock.
    file1.lock_shared().unwrap();
    assert_eq!(
      file2.try_lock_exclusive().unwrap_err().raw_os_error(),
      lock_contended_error().raw_os_error()
    );

    // Once the original file handle is unlocked, the duplicate handle can proceed with a lock.
    file1.unlock().unwrap();
    file2.lock_exclusive().unwrap();
  }

  /// A file handle may not be exclusively locked multiple times, or exclusively locked and then
  /// shared locked.
  #[test]
  fn lock_non_reentrant() {
    let tempdir = tempdir::TempDir::new("fs2").unwrap();
    let path = tempdir.path().join("fs2");
    let file = fs::OpenOptions::new()
      .read(true)
      .write(true)
      .create(true)
      .open(&path)
      .unwrap();

    // Multiple exclusive locks fails.
    file.lock_exclusive().unwrap();
    assert_eq!(
      file.try_lock_exclusive().unwrap_err().raw_os_error(),
      lock_contended_error().raw_os_error()
    );
    file.unlock().unwrap();

    // Shared then Exclusive locks fails.
    file.lock_shared().unwrap();
    assert_eq!(
      file.try_lock_exclusive().unwrap_err().raw_os_error(),
      lock_contended_error().raw_os_error()
    );
  }

  /// A file handle can hold an exclusive lock and any number of shared locks, all of which must
  /// be unlocked independently.
  #[test]
  fn lock_layering() {
    let tempdir = tempdir::TempDir::new("fs2").unwrap();
    let path = tempdir.path().join("fs2");
    let file = fs::OpenOptions::new()
      .read(true)
      .write(true)
      .create(true)
      .open(&path)
      .unwrap();

    // Open two shared locks on the file, and then try and fail to open an exclusive lock.
    file.lock_exclusive().unwrap();
    file.lock_shared().unwrap();
    file.lock_shared().unwrap();
    assert_eq!(
      file.try_lock_exclusive().unwrap_err().raw_os_error(),
      lock_contended_error().raw_os_error()
    );

    // Pop one of the shared locks and try again.
    file.unlock().unwrap();
    assert_eq!(
      file.try_lock_exclusive().unwrap_err().raw_os_error(),
      lock_contended_error().raw_os_error()
    );

    // Pop the second shared lock and try again.
    file.unlock().unwrap();
    assert_eq!(
      file.try_lock_exclusive().unwrap_err().raw_os_error(),
      lock_contended_error().raw_os_error()
    );

    // Pop the exclusive lock and finally succeed.
    file.unlock().unwrap();
    file.lock_exclusive().unwrap();
  }

  /// A file handle with multiple open locks will have all locks closed on drop.
  #[test]
  fn lock_layering_cleanup() {
    let tempdir = tempdir::TempDir::new("fs2").unwrap();
    let path = tempdir.path().join("fs2");
    let file1 = fs::OpenOptions::new()
      .read(true)
      .write(true)
      .create(true)
      .open(&path)
      .unwrap();
    let file2 = fs::OpenOptions::new()
      .read(true)
      .write(true)
      .create(true)
      .open(&path)
      .unwrap();

    // Open two shared locks on the file, and then try and fail to open an exclusive lock.
    file1.lock_shared().unwrap();
    assert_eq!(
      file2.try_lock_exclusive().unwrap_err().raw_os_error(),
      lock_contended_error().raw_os_error()
    );

    drop(file1);
    file2.lock_exclusive().unwrap();
  }

  /// A file handle's locks will not be released until the original handle and all of its
  /// duplicates have been closed. This on really smells like a bug in Windows.
  #[test]
  fn lock_duplicate_cleanup() {
    let tempdir = tempdir::TempDir::new("fs2").unwrap();
    let path = tempdir.path().join("fs2");
    let file1 = fs::OpenOptions::new()
      .read(true)
      .write(true)
      .create(true)
      .open(&path)
      .unwrap();
    let file2 = file1.duplicate().unwrap();

    // Open a lock on the original handle, then close it.
    file1.lock_shared().unwrap();
    drop(file1);

    // Attempting to create a lock on the file with the duplicate handle will fail.
    assert_eq!(
      file2.try_lock_exclusive().unwrap_err().raw_os_error(),
      lock_contended_error().raw_os_error()
    );
  }
}
