/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.sshd.common.file.nativefs;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.GroupPrincipal;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.UserPrincipal;
import java.nio.file.attribute.UserPrincipalLookupService;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.sshd.common.file.SshFile;

/**
 * <strong>Internal class, do not use directly.</strong>
 * 
 * This class wraps native file object.
 *
 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
 */
public class NativeSshFileNio extends NativeSshFile {

    /**
     * Constructor, internal do not use directly.
     * @param nativeFileSystemView
     */
    public NativeSshFileNio(NativeFileSystemView nativeFileSystemView, String fileName, File file, String userName) {
        super(nativeFileSystemView, fileName, file, userName);
    }

    public Map<Attribute, Object> getAttributes(boolean followLinks) throws IOException {
        String[] attrs = new String[] { "unix:*", "posix:*", "*" };
        Map<String, Object> a = null;
        for (String attr : attrs) {
            try {
                a = Files.readAttributes(
                        file.toPath(), attr,
                        followLinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS});
                break;
            } catch (UnsupportedOperationException e) {
                // Ignore
            }
        }
        if (a == null) {
            throw new IllegalStateException();
        }
        Map<Attribute, Object> map = new HashMap<Attribute, Object>();
        map.put(Attribute.Size, a.get("size"));
        if (a.containsKey("uid")) {
            map.put(Attribute.Uid, a.get("uid"));
        }
        if (a.containsKey("owner")) {
            map.put(Attribute.Owner, ((UserPrincipal) a.get("owner")).getName());
        } else {
            map.put(Attribute.Owner, userName);
        }
        if (a.containsKey("gid")) {
            map.put(Attribute.Gid, a.get("gid"));
        }
        if (a.containsKey("group")) {
            map.put(Attribute.Group, ((GroupPrincipal) a.get("group")).getName());
        } else {
            map.put(Attribute.Group, userName);
        }
        map.put(Attribute.IsDirectory, a.get("isDirectory"));
        map.put(Attribute.IsRegularFile, a.get("isRegularFile"));
        map.put(Attribute.IsSymbolicLink, a.get("isSymbolicLink"));
        map.put(Attribute.CreationTime, ((FileTime) a.get("creationTime")).toMillis());
        map.put(Attribute.LastModifiedTime, ((FileTime) a.get("lastModifiedTime")).toMillis());
        map.put(Attribute.LastAccessTime, ((FileTime) a.get("lastAccessTime")).toMillis());
        if (a.containsKey("permissions")) {
            map.put(Attribute.Permissions, fromPerms((Set<PosixFilePermission>) a.get("permissions")));
        } else {
            EnumSet<Permission> p = EnumSet.noneOf(Permission.class);
            if (isReadable()) {
                p.add(Permission.UserRead);
                p.add(Permission.GroupRead);
                p.add(Permission.OthersRead);
            }
            if (isWritable()) {
                p.add(Permission.UserWrite);
                p.add(Permission.GroupWrite);
                p.add(Permission.OthersWrite);
            }
            if (isExecutable()) {
                p.add(Permission.UserExecute);
                p.add(Permission.GroupExecute);
                p.add(Permission.OthersExecute);
            }
            map.put(Attribute.Permissions, p);
        }
        return map;
    }

    public void setAttributes(Map<Attribute, Object> attributes) throws IOException {
        Set<Attribute> unsupported = new HashSet<Attribute>();
        for (Attribute attribute : attributes.keySet()) {
            String name = null;
            Object value = attributes.get(attribute);
            switch (attribute) {
                case Size:             {
                    long newSize = (Long) value;
                    FileChannel outChan = new FileOutputStream(file, true).getChannel();
                    outChan.truncate(newSize);
                    outChan.close();
                    continue;
                }
                case Uid:              name = "unix:uid"; break;
                case Gid:              name = "unix:gid"; break;
                case Owner:            name = "posix:owner"; value = toUser((String) value); break;
                case Group:            name = "posix:group"; value = toGroup((String) value); break;
                case Permissions:      name = "posix:permissions"; value = toPerms((EnumSet<Permission>) value); break;
                case CreationTime:     name = "basic:creationTime"; value = FileTime.fromMillis((Long) value); break;
                case LastModifiedTime: name = "basic:lastModifiedTime"; value = FileTime.fromMillis((Long) value); break;
                case LastAccessTime:   name = "basic:lastAccessTime"; value = FileTime.fromMillis((Long) value); break;
            }
            if (name != null && value != null) {
                try {
                    Files.setAttribute(file.toPath(), name, value, LinkOption.NOFOLLOW_LINKS);
                } catch (UnsupportedOperationException e) {
                    unsupported.add(attribute);
                }
            }
        }
        handleUnsupportedAttributes(unsupported);
    }

    public String readSymbolicLink() throws IOException {
        Path path = file.toPath();
        Path link = Files.readSymbolicLink(path);
        return link.toString();
    }

    public void createSymbolicLink(SshFile destination) throws IOException {
        Path link = file.toPath();
        Path target = Paths.get(destination.getAbsolutePath());
        Files.createSymbolicLink(target, link);
    }

    private EnumSet<Permission> fromPerms(Set<PosixFilePermission> perms) {
        EnumSet<Permission> p = EnumSet.noneOf(Permission.class);
        for (PosixFilePermission perm : perms) {
            switch (perm) {
                case OWNER_READ:     p.add(Permission.UserRead); break;
                case OWNER_WRITE:    p.add(Permission.UserWrite); break;
                case OWNER_EXECUTE:  p.add(Permission.UserExecute); break;
                case GROUP_READ:     p.add(Permission.GroupRead); break;
                case GROUP_WRITE:    p.add(Permission.GroupWrite); break;
                case GROUP_EXECUTE:  p.add(Permission.GroupExecute); break;
                case OTHERS_READ:    p.add(Permission.OthersRead); break;
                case OTHERS_WRITE:   p.add(Permission.OthersWrite); break;
                case OTHERS_EXECUTE: p.add(Permission.OthersExecute); break;
            }
        }
        return p;
    }

    private GroupPrincipal toGroup(String name) throws IOException {
        UserPrincipalLookupService lookupService = file.toPath().getFileSystem().getUserPrincipalLookupService();
        return lookupService.lookupPrincipalByGroupName(name);
    }

    private UserPrincipal toUser(String name) throws IOException {
        UserPrincipalLookupService lookupService = file.toPath().getFileSystem().getUserPrincipalLookupService();
        return lookupService.lookupPrincipalByName(name);
    }

    private Set<PosixFilePermission> toPerms(EnumSet<Permission> perms) {
        Set<PosixFilePermission> set = new HashSet<PosixFilePermission>();
        for (Permission p : perms) {
            switch (p) {
                case UserRead:      set.add(PosixFilePermission.OWNER_READ); break;
                case UserWrite:     set.add(PosixFilePermission.OWNER_WRITE); break;
                case UserExecute:   set.add(PosixFilePermission.OWNER_EXECUTE); break;
                case GroupRead:     set.add(PosixFilePermission.GROUP_READ); break;
                case GroupWrite:    set.add(PosixFilePermission.GROUP_WRITE); break;
                case GroupExecute:  set.add(PosixFilePermission.GROUP_EXECUTE); break;
                case OthersRead:    set.add(PosixFilePermission.OTHERS_READ); break;
                case OthersWrite:   set.add(PosixFilePermission.OTHERS_WRITE); break;
                case OthersExecute: set.add(PosixFilePermission.OTHERS_EXECUTE); break;
            }
        }
        return set;
    }

}
