package jnr.posix;

import jnr.constants.platform.OpenFlags;
import jnr.constants.platform.Fcntl;
import jnr.constants.platform.Errno;
import jnr.constants.platform.WaitFlags;
import static jnr.constants.platform.Errno.*;
import static jnr.constants.platform.windows.LastError.*;

import jnr.ffi.LastError;
import jnr.ffi.Pointer;
import jnr.ffi.byref.IntByReference;
import jnr.ffi.mapper.FromNativeContext;

import java.io.FileDescriptor;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;

import jnr.posix.util.MethodName;
import jnr.posix.util.Platform;
import jnr.posix.util.WindowsHelpers;
import jnr.posix.windows.CommonFileInformation;
import jnr.posix.windows.WindowsByHandleFileInformation;
import jnr.posix.windows.WindowsFileInformation;
import jnr.posix.windows.WindowsFindData;

final public class WindowsPOSIX extends BaseNativePOSIX {
    private final static int FILE_TYPE_CHAR = 0x0002;

    private final static Map<Integer, Errno> errorToErrnoMapper
            = new HashMap<Integer, Errno>();

    static {
        errorToErrnoMapper.put(ERROR_INVALID_FUNCTION.value(), EINVAL);
        errorToErrnoMapper.put(ERROR_FILE_NOT_FOUND.value(), ENOENT);
        errorToErrnoMapper.put(ERROR_PATH_NOT_FOUND.value(), ENOENT);
        errorToErrnoMapper.put(ERROR_TOO_MANY_OPEN_FILES.value(), EMFILE);
        errorToErrnoMapper.put(ERROR_ACCESS_DENIED.value(), EACCES);
        errorToErrnoMapper.put(ERROR_INVALID_HANDLE.value(), EBADF );
        errorToErrnoMapper.put(ERROR_ARENA_TRASHED.value(), ENOMEM);
        errorToErrnoMapper.put(ERROR_NOT_ENOUGH_MEMORY.value(), ENOMEM);
        errorToErrnoMapper.put(ERROR_INVALID_BLOCK.value(), ENOMEM);
        errorToErrnoMapper.put(ERROR_BAD_ENVIRONMENT.value(), E2BIG );
        errorToErrnoMapper.put(ERROR_BAD_FORMAT.value(), ENOEXEC);
        errorToErrnoMapper.put(ERROR_INVALID_ACCESS.value(), EINVAL);
        errorToErrnoMapper.put(ERROR_INVALID_DATA.value(), EINVAL);
        errorToErrnoMapper.put(ERROR_INVALID_DRIVE.value(), ENOENT);
        errorToErrnoMapper.put(ERROR_CURRENT_DIRECTORY.value(), EACCES);
        errorToErrnoMapper.put(ERROR_NOT_SAME_DEVICE.value(), EXDEV );
        errorToErrnoMapper.put(ERROR_NO_MORE_FILES.value(), ENOENT);
        errorToErrnoMapper.put(ERROR_WRITE_PROTECT.value(), EROFS );
        errorToErrnoMapper.put(ERROR_BAD_UNIT.value(), ENODEV);
        errorToErrnoMapper.put(ERROR_NOT_READY.value(), ENXIO );
        errorToErrnoMapper.put(ERROR_BAD_COMMAND.value(), EACCES);
        errorToErrnoMapper.put(ERROR_CRC.value(), EACCES);
        errorToErrnoMapper.put(ERROR_BAD_LENGTH.value(), EACCES);
        errorToErrnoMapper.put(ERROR_SEEK.value(), EIO);
        errorToErrnoMapper.put(ERROR_NOT_DOS_DISK.value(), EACCES);
        errorToErrnoMapper.put(ERROR_SECTOR_NOT_FOUND.value(), EACCES);
        errorToErrnoMapper.put(ERROR_OUT_OF_PAPER.value(), EACCES);
        errorToErrnoMapper.put(ERROR_WRITE_FAULT.value(), EIO);
        errorToErrnoMapper.put(ERROR_READ_FAULT.value(), EIO);
        errorToErrnoMapper.put(ERROR_GEN_FAILURE.value(), EACCES);
        errorToErrnoMapper.put(ERROR_LOCK_VIOLATION.value(), EACCES);
        errorToErrnoMapper.put(ERROR_SHARING_VIOLATION.value(), EACCES);
        errorToErrnoMapper.put(ERROR_WRONG_DISK.value(), EACCES);
        errorToErrnoMapper.put(ERROR_SHARING_BUFFER_EXCEEDED.value(), EACCES);
        errorToErrnoMapper.put(ERROR_BAD_NETPATH.value(), ENOENT);
        errorToErrnoMapper.put(ERROR_NETWORK_ACCESS_DENIED.value(), EACCES);
        errorToErrnoMapper.put(ERROR_BAD_NET_NAME.value(), ENOENT);
        errorToErrnoMapper.put(ERROR_FILE_EXISTS.value(), EEXIST);
        errorToErrnoMapper.put(ERROR_CANNOT_MAKE.value(), EACCES);
        errorToErrnoMapper.put(ERROR_FAIL_I24.value(), EACCES);
        errorToErrnoMapper.put(ERROR_INVALID_PARAMETER.value(), EINVAL);
        errorToErrnoMapper.put(ERROR_NO_PROC_SLOTS.value(), EAGAIN);
        errorToErrnoMapper.put(ERROR_DRIVE_LOCKED.value(), EACCES);
        errorToErrnoMapper.put(ERROR_BROKEN_PIPE.value(), EPIPE);
        errorToErrnoMapper.put(ERROR_DISK_FULL.value(), ENOSPC);
        errorToErrnoMapper.put(ERROR_INVALID_TARGET_HANDLE.value(), EBADF);
        errorToErrnoMapper.put(ERROR_INVALID_HANDLE.value(), EINVAL);
        errorToErrnoMapper.put(ERROR_WAIT_NO_CHILDREN.value(), ECHILD);
        errorToErrnoMapper.put(ERROR_CHILD_NOT_COMPLETE.value(), ECHILD);
        errorToErrnoMapper.put(ERROR_DIRECT_ACCESS_HANDLE.value(), EBADF);
        errorToErrnoMapper.put(ERROR_NEGATIVE_SEEK.value(), EINVAL);
        errorToErrnoMapper.put(ERROR_SEEK_ON_DEVICE.value(), EACCES);
        errorToErrnoMapper.put(ERROR_DIR_NOT_EMPTY.value(), ENOTEMPTY);
        errorToErrnoMapper.put(ERROR_DIRECTORY.value(), ENOTDIR);
        errorToErrnoMapper.put(ERROR_NOT_LOCKED.value(), EACCES);
        errorToErrnoMapper.put(ERROR_BAD_PATHNAME.value(), ENOENT);
        errorToErrnoMapper.put(ERROR_MAX_THRDS_REACHED.value(), EAGAIN);
        errorToErrnoMapper.put(ERROR_LOCK_FAILED.value(), EACCES);
        errorToErrnoMapper.put(ERROR_ALREADY_EXISTS.value(), EEXIST);
        errorToErrnoMapper.put(ERROR_INVALID_STARTING_CODESEG.value(), ENOEXEC);
        errorToErrnoMapper.put(ERROR_INVALID_STACKSEG.value(), ENOEXEC);
        errorToErrnoMapper.put(ERROR_INVALID_MODULETYPE.value(), ENOEXEC);
        errorToErrnoMapper.put(ERROR_INVALID_EXE_SIGNATURE.value(), ENOEXEC);
        errorToErrnoMapper.put(ERROR_EXE_MARKED_INVALID.value(), ENOEXEC);
        errorToErrnoMapper.put(ERROR_BAD_EXE_FORMAT.value(), ENOEXEC);
        errorToErrnoMapper.put(ERROR_ITERATED_DATA_EXCEEDS_64k.value(), ENOEXEC);
        errorToErrnoMapper.put(ERROR_INVALID_MINALLOCSIZE.value(), ENOEXEC);
        errorToErrnoMapper.put(ERROR_DYNLINK_FROM_INVALID_RING.value(),ENOEXEC);
        errorToErrnoMapper.put(ERROR_IOPL_NOT_ENABLED.value(), ENOEXEC);
        errorToErrnoMapper.put(ERROR_INVALID_SEGDPL.value(), ENOEXEC);
        errorToErrnoMapper.put(ERROR_AUTODATASEG_EXCEEDS_64k.value(), ENOEXEC);
        errorToErrnoMapper.put(ERROR_RING2SEG_MUST_BE_MOVABLE.value(), ENOEXEC);
        errorToErrnoMapper.put(ERROR_RELOC_CHAIN_XEEDS_SEGLIM.value(), ENOEXEC);
        errorToErrnoMapper.put(ERROR_INFLOOP_IN_RELOC_CHAIN.value(), ENOEXEC);
        errorToErrnoMapper.put(ERROR_FILENAME_EXCED_RANGE.value(), ENOENT);
        errorToErrnoMapper.put(ERROR_NESTING_NOT_ALLOWED.value(), EAGAIN);
        // ERROR_PIPE_LOCAL (in MRI)
        errorToErrnoMapper.put(229, EPIPE);
        errorToErrnoMapper.put(ERROR_BAD_PIPE.value(), EPIPE);
        errorToErrnoMapper.put(ERROR_PIPE_BUSY.value(), EAGAIN);
        errorToErrnoMapper.put(ERROR_NO_DATA.value(), EPIPE);
        errorToErrnoMapper.put(ERROR_PIPE_NOT_CONNECTED.value(), EPIPE);
        errorToErrnoMapper.put(ERROR_OPERATION_ABORTED.value(), EINTR);
        errorToErrnoMapper.put(ERROR_NOT_ENOUGH_QUOTA.value(), ENOMEM);
        errorToErrnoMapper.put(ERROR_MOD_NOT_FOUND.value(), ENOENT);
        errorToErrnoMapper.put(WSAENAMETOOLONG.value(), ENAMETOOLONG);
        errorToErrnoMapper.put(WSAENOTEMPTY.value(), ENOTEMPTY);
        errorToErrnoMapper.put(WSAEINTR.value(), EINTR);
        errorToErrnoMapper.put(WSAEBADF.value(), EBADF);
        errorToErrnoMapper.put(WSAEACCES.value(), EACCES);
        errorToErrnoMapper.put(WSAEFAULT.value(), EFAULT);
        errorToErrnoMapper.put(WSAEINVAL.value(), EINVAL);
        errorToErrnoMapper.put(WSAEMFILE.value(), EMFILE);
    }

    private final FileStat checkFdStat;

    WindowsPOSIX(LibCProvider libc, POSIXHandler handler) {
        super(libc, handler);
        this.checkFdStat = new WindowsFileStat(this);
    }
    
    @Override
    public FileStat allocateStat() {
        return new WindowsRawFileStat(this, handler);
    }

    public MsgHdr allocateMsgHdr() {
        handler.unimplementedError(MethodName.getCallerMethodName());
        return null;
    }

    public SocketMacros socketMacros() {
        handler.unimplementedError(MethodName.getCallerMethodName());
        return null;
    }

    @Override
    public int kill(int pid, int signal) {
        handler.unimplementedError("kill");

        return -1;
    }

    @Override
    public int kill(long pid, int signal) {
        handler.unimplementedError("kill");

        return -1;
    }

    @Override
    public int chmod(String filename, int mode) {
        return wlibc()._wchmod(WString.path(filename), mode);
    }

    @Override
    public int chdir(String path) {
        return wlibc()._wchdir(WString.path(path));
    }
    
    @Override
    public int chown(String filename, int user, int group) {
        return 0;
    }
    
    @Override
    public int exec(String path, String[] argv) {
        if (argv.length == 1) return spawn(true, argv[0], null, path, null);

        return aspawn(true, null, argv, path, null);
    }

    @Override
    public CharSequence crypt(CharSequence key, CharSequence salt) {
        return JavaLibCHelper.crypt(key, salt);
    }

    @Override
    public byte[] crypt(byte[] key, byte[] salt) {
        return JavaLibCHelper.crypt(key, salt);
    }

    @Override
    public int exec(String path, String[] argv, String[] envp) {
        if (argv.length == 1) return spawn(true, argv[0], null, path, envp);

        return aspawn(true, null, argv, path, envp);
    }
    
    @Override
    public int execv(String path, String[] argv) {
        handler.unimplementedError("egid");

        return -1;
    }

    @Override
    public int getegid() {
        handler.unimplementedError("egid");

        return -1;
    }

    @Override
    public int setegid(int egid) {
        handler.unimplementedError("setegid");

        return -1;
    }

    @Override
    public int geteuid() {
        return 0;
    }

    @Override
    public int seteuid(int euid) {
        handler.unimplementedError("seteuid");

        return -1;
    }

    @Override
    public int getuid() {
        return 0;
    }

    @Override
    public int setuid(int uid) {
        handler.unimplementedError("setuid");

        return -1;
    }

    @Override
    public int getgid() {
        return 0;
    }

    @Override
    public int setgid(int gid) {
        handler.unimplementedError("setgid");

        return -1;
    }

    @Override
    public int getpgid(int pid) {
        handler.unimplementedError("getpgid");

        return -1;
    }
    
    @Override
    public int getpgid() {
        handler.unimplementedError("getpgid");

        return -1;
    }

    @Override
    public int setpgid(int pid, int pgid) {
        handler.unimplementedError("setpgid");

        return -1;
    }
    
    @Override
    public int getpriority(int which, int who) {
        handler.unimplementedError("getpriority");

        return -1;
    }
    
    @Override
    public int setpriority(int which, int who, int prio) {
        handler.unimplementedError("setpriority");

        return -1;
    }

    @Override
    public int getpid(){
        return wlibc()._getpid();
    }

    @Override
    public int getppid() {
        return 0;
    }
    
    @Override
    public int lchmod(String filename, int mode) {
        handler.unimplementedError("lchmod");
        
        return -1;
    }
    
    @Override
    public int lchown(String filename, int user, int group) {
        handler.unimplementedError("lchown");
        
        return -1;
    }

    @Override
    public String gethostname() {
        ByteBuffer buffer = ByteBuffer.allocate(64);
        IntByReference len = new IntByReference(buffer.capacity() - 1);
        if (!wlibc().GetComputerNameW(buffer, len)) return helper.gethostname();
        buffer.limit(len.intValue() * 2);
        return Charset.forName("UTF-16LE").decode(buffer).toString();
    }
    
    public FileStat fstat(int fd) {
        WindowsFileStat stat = new WindowsFileStat(this);
        if (fstat(fd, stat) < 0) handler.error(Errno.valueOf(errno()), "fstat", "" + fd);
        return stat;
    }
    
    @Override
    public int fstat(FileDescriptor fileDescriptor, FileStat stat) {
        WindowsByHandleFileInformation info = new WindowsByHandleFileInformation(getRuntime());
        if (wlibc().GetFileInformationByHandle(JavaLibCHelper.gethandle(fileDescriptor), info) == 0) return -1;

        ((WindowsRawFileStat) stat).setup(info);

        return 0;
    }
    
    @Override
    public FileStat lstat(String path) {
        return stat(path);
    }

    @Override
    public int lstat(String path, FileStat stat) {
        return stat(path, stat); // windows stat honors windows equiv of softlinks and dangling ones.
    }

    @Override
    public int stat(String path, FileStat stat) {
        WindowsFileInformation info = new WindowsFileInformation(getRuntime());
        byte[] wpath = WString.path(path, true);

        if (wlibc().GetFileAttributesExW(wpath, 0, info) != 0) {
            ((WindowsRawFileStat) stat).setup(path, info);
        } else {
            int e = errno();

            if (e == ERROR_FILE_NOT_FOUND.intValue() || e == ERROR_PATH_NOT_FOUND.intValue()
                    || e == ERROR_BAD_NETPATH.intValue()) {
                return -1;
            }

            return findFirstFile(path, stat);
        }

        return 0;
    }

    // Public so we can test this via unit-testing.  This makes me wish we had a whole different interface for
    // windows APIs user32/kernel32 that this class could consume easily.  We are clearly missing an abstraction
    // or project here.
    public int findFirstFile(String path, FileStat stat) {
        byte[] wpath = WString.path(path, true);
        WindowsFindData findData = new WindowsFindData(getRuntime());
        HANDLE handle = wlibc().FindFirstFileW(wpath, findData);
        if (!handle.isValid()) return -1;
        wlibc().FindClose(handle);
        ((WindowsRawFileStat) stat).setup(path, findData);

        return 0;
    }

    @Override
    public String readlink(String oldpath) {
        handler.unimplementedError("readlink");

        return null;
    }

    @Override
    public Pointer environ() {
        return getRuntime().getMemoryManager().newPointer(wlibc()._environ().get());
    }

    @Override
    public int setenv(String envName, String envValue, int overwrite) {
        if (envName.contains("=")) {
            handler.error(EINVAL, "setenv", envName);
            return -1;
        }

        // FIXME: We do not have getenv implemented yet.  So we are ignoring for now
        // POSIX specified.  Existence is success if overwrite is 0.
        // if (overwrite == 0 && getenv(envName) != null) return 0;
        
        if (!wlibc().SetEnvironmentVariableW(new WString(envName), new WString(envValue))) {
            handler.error(EINVAL, "setenv", envName);
            return -1;
        }

        return 0;
    }

    @Override
    public int umask(int mask) {
        return wlibc()._umask(mask);
    }
    
    @Override
    public int unsetenv(String envName) {
        if (!wlibc().SetEnvironmentVariableW(new WString(envName), null)) {
            handler.error(EINVAL, "unsetenv", envName);
            return -1;
        }
        
        return 0;
    }



    private static final int GENERIC_ALL = 0x10000000;
    private static final int GENERIC_READ = 0x80000000;
    private static final int GENERIC_WRITE = 0x40000000;
    private static final int GENERIC_EXECUTE = 0x2000000;

    private static final int FILE_SHARE_DELETE = 0x00000004;
    private static final int FILE_SHARE_READ =  0x00000001;
    private static final int FILE_SHARE_WRITE =  0x00000002;

    private static final int CREATE_ALWAYS = 2;
    private static final int CREATE_NEW = 1;
    private static final int OPEN_ALWAYS = 4;
    private static final int OPEN_EXISTING = 3;
    private static final int TRUNCATE_EXISTING = 5;

    public static final int FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;

    @Override
    public int utimes(String path, long[] atimeval, long[] mtimeval) {
        FileTime aTime = timevalToFileTime(atimeval);
        FileTime mTime = timevalToFileTime(mtimeval);
        return setFileTime(path, aTime, mTime);
    }

    @Override
    public int utimensat(int dirfd, String path, long[] atimespec, long[] mtimespec, int flag) {
        FileTime aTime = timespecToFileTime(atimespec);
        FileTime mTime = timespecToFileTime(mtimespec);
        return setFileTime(path, aTime, mTime);
    }

    private FileTime timevalToFileTime(long[] timeval) {
        if (timeval == null) {
            return currentFileTime();
        }

        // timeval unit is (sec, microsec)
        long unixEpochIn100ns = timeval[0] * 10000000 + timeval[1] * 10;
        return unixTimeToFileTime(unixEpochIn100ns);
    }

    private FileTime timespecToFileTime(long[] timespec) {
        if (timespec == null) {
            return currentFileTime();
        }

        // timespec unit is (sec, nanosec)
        long unixEpochIn100ns = timespec[0] * 10000000 + timespec[1] / 100;
        return unixTimeToFileTime(unixEpochIn100ns);
    }

    private int setFileTime(String path, FileTime aTime, FileTime mTime) {
        byte[] wpath = WindowsHelpers.toWPath(path);
        HANDLE handle = wlibc().CreateFileW(wpath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
                null, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
        if (!handle.isValid()) {
            return -1;             // TODO proper error handling
        }

        boolean timeSet = wlibc().SetFileTime(handle, null, aTime, mTime);
        wlibc().CloseHandle(handle);

        return timeSet ? 0 : -1;
    }

    /**
     *
     * @param unixEpochIn100ns epoch time in 100-ns precision
     * @return associated FILETIME structure
     */
    private FileTime unixTimeToFileTime(long unixEpochIn100ns) {
        // FILETIME is a 64-bit unsigned integer representing
        // the number of 100-nanosecond intervals since January 1, 1601
        // UNIX timestamp is number of seconds since January 1, 1970
        // 116444736000000000 = 10_000_000 * 60 * 60 * 24 * 365 * 369 + 89 leap days
        long ft = 116444736000000000L + unixEpochIn100ns;

        //long ft = CommonFileInformation.asNanoSeconds(unixTimeSeconds);
        FileTime fileTime = new FileTime(getRuntime());
        fileTime.dwLowDateTime.set(ft & 0xFFFFFFFFL);
        fileTime.dwHighDateTime.set((ft >> 32) & 0xFFFFFFFFL);
        return fileTime;
    }

    private FileTime nullFileTime() {
        FileTime fileTime = new FileTime(getRuntime());
        fileTime.dwLowDateTime.set(0);
        fileTime.dwHighDateTime.set(0);
        return fileTime;
    }

    private FileTime currentFileTime() {
      return unixTimeToFileTime(System.currentTimeMillis() * 10000);
    }

    @Override
    public int wait(int[] status) {
        handler.unimplementedError("wait");

        return -1;
    }

    @Override
    public int waitpid(int pid, int[] status, int flags) {
        if (pid <= 0) {
            handler.unimplementedError("waitpid");
        }

        HANDLE h = wlibc().OpenProcess(WindowsLibC.PROCESS_QUERY_INFORMATION, 0, pid);
        if (h == null) {
            return -1; // TODO: Throw exception
        }

        // Block
        if ((flags & WaitFlags.WNOHANG.intValue()) != 0) {
            wlibc().WaitForSingleObject(h, WindowsLibC.INFINITE);
        }

        IntByReference exitCode = new IntByReference();
        wlibc().GetExitCodeProcess(h, exitCode);
        wlibc().CloseHandle(h);
        int code = exitCode.getValue();
        if (code == 259) {
            return 0;
        } else {
            status[0] = code;
            return pid;
        }
    }

    @Override
    public int waitpid(long pid, int[] status, int flags) {
        if (pid > Integer.MAX_VALUE) {
            throw new java.lang.IllegalArgumentException("waitpid");
        }
        return waitpid((int) pid, status, flags);
    }

    @Override
    public String getlogin() {
        return helper.getlogin();
    }

    @Override
    public int endgrent() {
        return 0;
    }

    @Override
    public int endpwent() {
        return helper.endpwent();
    }

    @Override
    public Group getgrent() {
        return null;
    }

    @Override
    public Passwd getpwent() {
        return null;
    }

    @Override
    public Group getgrgid(int which) {
        return null;
    }

    @Override
    public Passwd getpwnam(String which) {
        return null;
    }

    @Override
    public Group getgrnam(String which) {
        return null;
    }
    
    @Override
    public int setgrent() {
        return 0;
    }
    
    @Override
    public int setpwent() {
        return helper.setpwent();
    }

    @Override
    public Passwd getpwuid(int which) {
        return null;
    }

    @Override
    public boolean isatty(FileDescriptor fd) {
        HANDLE handle = JavaLibCHelper.gethandle(fd);

        int type = wlibc().GetFileType(handle);
        return type == FILE_TYPE_CHAR;
    }

    @Override
    public int isatty(int fd) {
        HANDLE handle = JavaLibCHelper.gethandle(fd);

        int type = wlibc().GetFileType(handle);
        return type == FILE_TYPE_CHAR ? 1 : 0;
    }

    @Override
    public int mkdir(String path, int mode) {
        WString widePath = WString.path(path);
        int res = -1;
        
        if (wlibc()._wmkdir(widePath) == 0) {
            res = wlibc()._wchmod(widePath, mode);
        }
        
        if (res < 0) {
            int errno = errno();
            handler.error(Errno.valueOf(errno), "mkdir", path);
        }
        return res;
    }
    
    // FIXME: Should this and other fields be part of constantine/jnr-constants?
    static final int FILE_ATTRIBUTE_READONLY = 1;
    static final int INVALID_FILE_ATTRIBUTES = -1;
    
    /**
     * The logic here is a bit strange and this copies MRI (Ruby) which may not be language
     * agnostic, but windows (win7 and others) automatically mark folders as read-only when 
     * it contains other files and folders within it.  This document explains more: 
     * http://support.microsoft.com/kb/326549
     * I think the logic is based around idea that if you removed all other files it would
     * be empty but will stay marked as read-only.
     *
     * @param path the path to remove
     * @return 0 if successful, -1 if failed
     */
    @Override
    public int rmdir(String path) {
        WString pathW = WString.path(path);
        int attr = wlibc().GetFileAttributesW(pathW);
        boolean isReadOnly = attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_READONLY) != 0;
        
        if (isReadOnly) wlibc().SetFileAttributesW(pathW, attr & ~FILE_ATTRIBUTE_READONLY);
        
        if (!wlibc().RemoveDirectoryW(pathW)) {
            int errno = errno();

            if (isReadOnly) wlibc().SetFileAttributesW(pathW, attr & FILE_ATTRIBUTE_READONLY);
            
            handler.error(mapErrorToErrno(errno), "rmdir", path);
            
            return -1;
        }

        return 0;
    }

    @Override
    public int link(String oldpath, String newpath) {
        boolean linkCreated =  wlibc().CreateHardLinkW(WString.path(newpath), WString.path(oldpath), null);

        if (!linkCreated) {
            int error = errno();
            handler.error(mapErrorToErrno(error), "link", oldpath + " or " + newpath);
            return error;
        } else {
            return 0;
        }
    }
    
    /**
     * @param overlay is P_OVERLAY if true and P_NOWAIT if false
     * @param program to be invoked
     * @param argv is all args including argv0 being what is executed
     * @param path is path to be searched when needed (delimited by ; on windows)
     * @param envp is a set of KEY=VALUE strings to be set in the child process
     * @return the pid
     */    
    public int aspawn(boolean overlay, String program, String[] argv, String path, String[] envp) {
        try {
        if (argv.length == 0) return -1;
        
        String[] cmds = WindowsHelpers.processCommandArgs(this, program, argv, path);
 
        return childResult(createProcess("aspawn", cmds[0], cmds[1], null, null, null, null, envp), overlay);
        } catch (Exception e) {
            return -1;
        }
    }

    public int pipe(int[] fds) {
        // TODO (nirvdrum 06-May-15) Maybe not hard-code the psize value. But figure out a sensible way to handle textmode.
        return ((WindowsLibC) libc())._pipe(fds, 512, 0);
    }

    public int truncate(CharSequence path, long length) {
        // Windows doesn't have a native truncate() equivalent, but it does have a native ftruncate() equivalent.
        // In order to call the ftruncate() equivalent, we must convert a path to a FD.  We do that by wrapping the
        // ftruncate() call with open() and close().

        // Permissions are ignored since we're not using O_CREAT.
        int fd = libc().open(path, OpenFlags.O_WRONLY.intValue(), 0);
        if (fd == -1) {
            return -1;
        }

        if (libc().ftruncate(fd, length) == -1) {
            return -1;
        }

        if (libc().close(fd) == -1) {
            return -1;
        }

        // truncate() returns 0 on success.
        return 0;
    }

    public int fcntlInt(int fd, Fcntl fcntl, int arg) {
        switch(fcntl) {
            case F_GETFD: {
                if (checkFd(fd) == -1) {
                    return -1;
                } else {
                    // This is a gigantic hack.  Indicate that Windows does not support close-on-exec.
                    return 0;
                }
            }

            case F_SETFD: {
                if (checkFd(fd) == -1) {
                    return -1;
                } else {
                    // This is a gigantic hack.  Indicate that Windows does not support close-on-exec by no-oping.
                    return 0;
                }
            }

            case F_GETFL: {
                if (checkFd(fd) == -1) {
                    return -1;
                } else {
                    // TODO (nirvdrum 06-May-15): Look up the actual flags rather than optimistically hard-coding this set.
                    return OpenFlags.O_RDWR.intValue();
                }
            }

            default: {
                handler.unimplementedError("fcntl");

                return -1;
            }
        }
    }
    
    private WindowsLibC wlibc() {
        return (WindowsLibC) libc();
    }
    
    /**
     * @param overlay is P_OVERLAY if true and P_NOWAIT if false
     * @param command full command string
     * @param program program to be invoked
     * @param path is path to be searched when needed (delimited by ; on windows)
     * @param envp is a set of KEY=VALUE strings to be set in the child process
     * @return the pid
     */
    public int spawn(boolean overlay, String command, String program, String path, String[] envp) {
        if (command == null) return -1;

        String[] cmds = WindowsHelpers.processCommandLine(this, command, program, path);

        return childResult(createProcess("spawn", cmds[0], cmds[1], null, null, null, null, envp), overlay);
    }
    
    private int childResult(WindowsChildRecord child, boolean overlay) {
        if (child == null) return -1;

        if (overlay) {
            IntByReference exitCode = new IntByReference();

            WindowsLibC libc = (WindowsLibC) libc();
            HANDLE handle = child.getProcess();
            
            libc.WaitForSingleObject(handle, WindowsLibC.INFINITE);
            libc.GetExitCodeProcess(handle, exitCode);
            libc.CloseHandle(handle);
            System.exit(exitCode.getValue());
        }

        return child.getPid();
    }

    private static Errno mapErrorToErrno(int error) {
        Errno errno = errorToErrnoMapper.get(error);
        if (errno == null) {
            errno = __UNKNOWN_CONSTANT__;
        }
        return errno;
    }

    private static final int STARTF_USESTDHANDLES = 0x00000100;
    
    // Used by spawn and aspawn (Note: See fixme below...envp not hooked up yet)
    private WindowsChildRecord createProcess(String callingMethodName, String command, String program, 
            WindowsSecurityAttributes securityAttributes, HANDLE input,
            HANDLE output, HANDLE error, String[] envp) {
        if (command == null && program == null) {
            handler.error(EFAULT, callingMethodName, "no command or program specified");
            return null;
        }
        
        if (securityAttributes == null) {
            securityAttributes = new WindowsSecurityAttributes(getRuntime());
        }
        
        WindowsStartupInfo startupInfo = new WindowsStartupInfo(getRuntime());
        
        startupInfo.setFlags(STARTF_USESTDHANDLES);
        startupInfo.setStandardInput(input != null ? input :
                wlibc().GetStdHandle(WindowsLibC.STD_INPUT_HANDLE));
        startupInfo.setStandardOutput(output != null ? output :
                wlibc().GetStdHandle(WindowsLibC.STD_OUTPUT_HANDLE));
        startupInfo.setStandardError(error != null ? input :
                wlibc().GetStdHandle(WindowsLibC.STD_ERROR_HANDLE));
        
        int creationFlags = WindowsLibC.NORMAL_PRIORITY_CLASS | WindowsLibC.CREATE_UNICODE_ENVIRONMENT;
        WindowsProcessInformation processInformation = new WindowsProcessInformation(getRuntime());

        // FIXME: Convert envp into useful wideEnv
        Pointer wideEnv = null;
        byte[] programW = WindowsHelpers.toWString(program);
        byte[] cwd = WindowsHelpers.toWString(WindowsHelpers.escapePath(handler.getCurrentWorkingDirectory().toString()) +"\\");
        ByteBuffer commandW = ByteBuffer.wrap(WindowsHelpers.toWString(command));
        boolean returnValue = wlibc().CreateProcessW(programW, commandW, 
                securityAttributes, securityAttributes, 
                securityAttributes.getInheritHandle() ? 1: 0, creationFlags, wideEnv, cwd, 
                startupInfo, processInformation);
        
        if (!returnValue) return null;
        
        wlibc().CloseHandle(processInformation.getThread());
        
        // TODO: On winnt reverse sign of pid
        return new WindowsChildRecord(processInformation.getProcess(), processInformation.getPid());
    }

    private int checkFd(int fd) {
        // There might be a lighter-weight check, but we basically want to
        // make sure the FD is valid and the effective user can access it,
        // since we need to simulate fcntl semantics.
        return libc().fstat(fd, checkFdStat);
    }

    public static final PointerConverter PASSWD = new PointerConverter() {
        public Object fromNative(Object arg, FromNativeContext ctx) {
            throw new RuntimeException("no support for native passwd");
        }
    };

    public int mkfifo(String filename, int mode) {
        handler.unimplementedError("mkfifo");

        return -1;
    }

    public Timeval allocateTimeval() {
        return new DefaultNativeTimeval(getRuntime());
    }

    // TODO: Replace with Win32 calls. See jnr/jnr-posix#98.
    public int gettimeofday(Timeval tv) {
        long currentMillis = System.currentTimeMillis();
        tv.sec(currentMillis / 1000);
        tv.usec(currentMillis % 1000 * 1000);
        return 0;
    }
}
