package jnr.posix;

import java.io.File;
import java.io.IOException;

import jnr.posix.POSIXHandler.WARNING_ID;

public class JavaFileStat implements FileStat {
    private final POSIXHandler handler;
    private final POSIX posix;

    short st_mode;
    int st_blksize;
    long st_size;
    int st_ctime;
    int st_mtime;
    
    
    public JavaFileStat(POSIX posix, POSIXHandler handler) {
        this.handler = handler;
        this.posix = posix;
    }
    
    public void setup(String path) {
        // ENEBO: This was originally JRubyFile, but I believe we use JRuby file for normalization
        // of paths.  So we should be safe.
        File file = new JavaSecuredFile(path);
        // Limitation: We cannot determine, so always return 4096 (better than blowing up)
        st_blksize = 4096;
        st_mode = calculateMode(file, st_mode);
        st_size = file.length();

        // Parent file last modified will only represent when something was added or removed.
        // This is not correct, but it is better than nothing and does work in one common use
        // case.
        st_mtime = (int) (file.lastModified() / 1000);
        if (file.getParentFile() != null) {
            st_ctime = (int) (file.getParentFile().lastModified() / 1000);
        } else {
            st_ctime = st_mtime;
        }
    }

    private short calculateMode(File file, short st_mode) {
        // implementation to lowest common denominator...
        // Windows has no file mode, but C ruby returns either 0100444 or 0100644

        if (file.canRead()) {
            st_mode |= ALL_READ;
        }

        if (file.canWrite()) {
            st_mode |= ALL_WRITE;
            st_mode &= ~(S_IWGRP | S_IWOTH);
        }

        if (file.isDirectory()) {
            st_mode |= S_IFDIR;
        } else if (file.isFile()) {
            st_mode |= S_IFREG;
        }
        
        try {
            st_mode = calculateSymlink(file, st_mode);
        } catch (IOException e) {
            // Not sure we can do much in this case...
        }
        
        return st_mode;
    }
    
    private short calculateSymlink(File file, short st_mode) throws IOException {
        if (file.getAbsoluteFile().getParentFile() == null) {
            return st_mode;
        }

        File absoluteParent = file.getAbsoluteFile().getParentFile();
        File canonicalParent = absoluteParent.getCanonicalFile();
        
        if (canonicalParent.getAbsolutePath().equals(absoluteParent.getAbsolutePath())) {
            // parent doesn't change when canonicalized, compare absolute and canonical file directly
            if (!file.getAbsolutePath().equalsIgnoreCase(file.getCanonicalPath())) {
                st_mode |= S_IFLNK;
                return st_mode;
            }
        }
        
        // directory itself has symlinks (canonical != absolute), so build new path with canonical parent and compare
        file = new JavaSecuredFile(canonicalParent.getAbsolutePath() + "/" + file.getName());
        if (!file.getAbsolutePath().equalsIgnoreCase(file.getCanonicalPath())) {
            st_mode |= S_IFLNK;
        }
        
        return st_mode;
    }

    /**
     * Limitation: Java has no access time support, so we return mtime as the next best thing.
     */
    public long atime() {
        return st_mtime;
    }

    public long blocks() {
        handler.unimplementedError("stat.st_blocks");
        
        return -1;
    }

    public long blockSize() {
        return st_blksize;
    }

    public long ctime() {
        return st_ctime;
    }
    
    public long dev() {
        handler.unimplementedError("stat.st_dev");
        
        return -1;
    }

    public String ftype() {
        if (isFile()) {
            return "file";
        } else if (isDirectory()) {
            return "directory";
        }
        
        return "unknown";
    }

    public int gid() {
        handler.unimplementedError("stat.st_gid");
        
        return -1;
    }
    
    public boolean groupMember(int gid) {
        return posix.getgid() == gid || posix.getegid() == gid;
    }

    /**
     *  Limitation: We have no pure-java way of getting inode.  webrick needs this defined to work.
     */
    public long ino() {
        return 0;
    }

    public boolean isBlockDev() {
        handler.unimplementedError("block device detection");
        
        return false;
    }
    
    /**
     * Limitation: [see JRUBY-1516] We just pick more likely value.  This is a little scary.
     */
    public boolean isCharDev() {
        return false;
    }
    
    public boolean isDirectory() {
        return (mode() & S_IFDIR) != 0;
    }

    public boolean isEmpty() {
        return st_size() == 0;
    }

    // At least one major library depends on this method existing.
    public boolean isExecutable() {
        handler.warn(WARNING_ID.DUMMY_VALUE_USED, "executable? does not in this environment and will return a dummy value", "executable");
        
        return true;
    }

    // At least one major library depends on this method existing.
    public boolean isExecutableReal() {
        handler.warn(WARNING_ID.DUMMY_VALUE_USED, "executable_real? does not work in this environmnt and will return a dummy value", "executable_real");
        
        return true;
    }

    public boolean isFifo() {
        handler.unimplementedError("fifo file detection");
        
        return false;
    }
    
    public boolean isFile() {
        return (mode() & S_IFREG) != 0;
    }
    
    public boolean isGroupOwned() {
        return groupMember(gid());
    }

    public boolean isIdentical(FileStat other) {
        handler.unimplementedError("identical file detection");
        
        return false;
    }

    public boolean isNamedPipe() {
        handler.unimplementedError("piped file detection");
        
        return false;
    }

    public boolean isOwned() {
        return posix.geteuid() == uid();
    }

    public boolean isROwned() {
        return posix.getuid() == uid();
    }

    public boolean isReadable() {
        int mode = mode();
        
        if ((mode & S_IRUSR) != 0) return true;
        if ((mode & S_IRGRP) != 0) return true;
        if ((mode & S_IROTH) != 0) return true;
        
        return false;
    }

    // We do both readable and readable_real through the same method because
    public boolean isReadableReal() {
        return isReadable();
    }

    public boolean isSymlink() {
        return (mode() & S_IFLNK) == S_IFLNK;
    }
    
    public boolean isWritable() {
        int mode = mode();
        
        if ((mode & S_IWUSR) != 0) return true;
        if ((mode & S_IWGRP) != 0) return true;
        if ((mode & S_IWOTH) != 0) return true;

        return false;
    }
    
    // We do both readable and readable_real through the same method because
    // in our java process effective and real userid will always be the same.
    public boolean isWritableReal() {
        return isWritable();
    }

    public boolean isSetgid() {
        handler.unimplementedError("setgid detection");
        
        return false;
    }

    public boolean isSetuid() {
        handler.unimplementedError("setuid detection");
        
        return false;
    }

    public boolean isSocket() {
        handler.unimplementedError("socket file type detection");
        
        return false;
    }
    
    public boolean isSticky() {
        handler.unimplementedError("sticky bit detection");
        
        return false;
    }

    public int major(long dev) {
        handler.unimplementedError("major device");
        
        return -1;
    }

    public int minor(long dev) {
        handler.unimplementedError("minor device");
        
        return -1;
    }

    public int mode() {
        return st_mode & 0xffff;
    }

    public long mtime() {
        return st_mtime;
    }

    public int nlink() {
        handler.unimplementedError("stat.nlink");
        
        return -1;
    }

    public long rdev() {
        handler.unimplementedError("stat.rdev");
        
        return -1;
    }
    
    public long st_size() {
        return st_size;
    }

    // Limitation: We have no pure-java way of getting uid. RubyZip needs this defined to work.
    public int uid() {
        return -1;
    }
}
