/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.io.uri.impl;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;
import java.util.Set;
import net.ranides.assira.collection.query.CQuery;
import net.ranides.assira.collection.query.CQueryBuilder;
import net.ranides.assira.io.IOHandle;
import net.ranides.assira.io.uri.URITime;
import net.ranides.assira.io.uri.URIFlags;
import net.ranides.assira.io.uri.URIHandle;
import net.ranides.assira.io.uri.URIHandler;
import net.ranides.assira.io.uri.URIResolver;
import net.ranides.assira.io.uri.URIUtils;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class CFileHandler implements URIHandler {
    
    private final URIResolver resolver;
    
    public CFileHandler() {
        this.resolver = URIResolver.DEFAULT;
    }

    public CFileHandler(URIResolver resolver) {
        this.resolver = resolver;
    }

    @Override
    public URIHandle resolve(URI uri) {
        return new CFileHandle(uri);
    }
    
    @Override
    public Collection<String> schemes() {
        return Arrays.asList("file");
    }

    private class CFileHandle extends CHandle {
        
        public final URI uri;
        public final File file;
        public final Path path;
        public final Charset charset;
        public final boolean text;
        
        public final IOHandle<InputStream> istream;
        public final IOHandle<OutputStream> ostream;

        public CFileHandle(URI uri) {
            this(uri, URIUtils.getFile(uri).toPath(), URIUtils.getParam(uri, "charset"));
        }
        
        public CFileHandle(Path path) {
            this(path.toUri(), path, Optional.empty());
        }
        
        public CFileHandle(URI uri, Path path, Optional<String> charset) {
            this.uri = uri;
            this.file = path.toFile();
            this.path = path;
            this.charset = Charset.forName(charset.orElse("UTF-8"));
            this.text = charset.isPresent();
            this.istream = IOHandle.shared(() -> Files.newInputStream(path));
            this.ostream = IOHandle.shared(() -> Files.newOutputStream(path));
        }

        @Override
        public void close() throws IOException {
            try {
                istream.close();
            } finally {
                ostream.close();
            }
            super.close();
        }
        
        @Override
        public URIResolver resolver() {
            return CFileHandler.this.resolver;
        }

        @Override
        public boolean exists() throws IOException {
            return Files.exists(path);
        }

        @Override
        public Charset charset() throws IOException {
            return charset;
        }

        @Override
        public Path path() throws IOException {
            return path;
        }

        @Override
        public File file() throws IOException {
            return file;
        }

        @Override
        public URI uri() throws IOException {
            return uri;
        }

        @Override
        public String scheme() throws IOException {
            return "file";
        }
        
        @Override
        public Set<URIFlags> flags() throws IOException {
            BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class);
            Set<URIFlags> flags = URIFlags.collect();

            // @todo (assira #3) CFileHandler: test
            // @todo (assira #3) CFileHandler: URIFlags.EXISTS
            
            if(attr.isDirectory()) {
                flags.add(URIFlags.DIR);
                flags.add(URIFlags.EXISTS);
            }
            if(attr.isRegularFile()) {
                flags.add(URIFlags.FILE);
                flags.add(URIFlags.SIZE);
                flags.add(URIFlags.SEEK);
            }
            if(Files.isReadable(path)) {
                flags.add(URIFlags.READABLE);
            }
            if(Files.isWritable(path)) {
                flags.add(URIFlags.WRITABLE);
            }
            if(Files.isExecutable(path)) {
                flags.add(URIFlags.EXECUTE);
            }
            if(text) {
                flags.add(URIFlags.TEXT);
            } else {
                flags.add(URIFlags.BINARY);
            }
            return flags;
        }

        @Override
        public Instant time(URITime ut) throws IOException {
            BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class);
            switch(ut) {
                case ACCESSED:
                    return attr.lastAccessTime().toInstant();
                case CREATED:
                    return attr.creationTime().toInstant();
                case MODIFIED:
                    return attr.lastModifiedTime().toInstant();
                case META_MODIFIED:
                    throw new UnsupportedOperationException("Not supported attribute: META_MODIFIED");
                default:
                    throw new UnsupportedOperationException("Not supported attribute: " + ut);
            }
        }

        @Override
        public long size() throws IOException {
            return Files.size(path);
        }

        @Override
        public URIHandle parent() throws IOException {
            return new CFileHandle(path.getParent());
        }

        @Override
        public CQuery<URIHandle> scan() throws IOException {
            return CQueryBuilder.fromStream(() -> Files.list(path).map(CFileHandle::new));
        }

        @Override
        public InputStream istream() throws IOException {
            return istream.get();
        }

        @Override
        public OutputStream ostream() throws IOException {
            return ostream.get();
        }

        @Override
        public void delete() throws IOException {
            Files.delete(path);
        }

        @Override
        public void create() throws IOException {
            Files.createDirectories(path.getParent());
            Files.createFile(path);
        }

        @Override
        public void move(URIHandle location) throws IOException {
            Files.move(path, location.path());
        }

        @Override
        public void copy(URIHandle location) throws IOException {
            if(location instanceof CFileHandle) {
                Files.copy(path, location.path(), StandardCopyOption.COPY_ATTRIBUTES);
            } else {
                super.copy(location);
            }
        }

    }
    
}
