package com.jscape.inet.sftp.protocol.v3.marshaling;

import com.jscape.inet.sftp.protocol.v3.messages.FileAttributeFlags;
import com.jscape.inet.sftp.protocol.v3.messages.FileAttributes;
import com.jscape.inet.ssh.protocol.v2.marshaling.primitive.StringCodec;
import com.jscape.inet.ssh.protocol.v2.marshaling.primitive.Uint32Codec;
import com.jscape.inet.ssh.protocol.v2.marshaling.primitive.Uint64Codec;
import com.jscape.util.file.PosixFilePermissions;
import com.jscape.util.file.PosixFilePermissionsFormat;
import com.jscape.util.file.UnixFileType;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author Alex Usun
 */
public class FileAttributesCodec {

    public static FileAttributes readValue(InputStream in)
            throws IOException {
        FileAttributeFlags flags = FileAttributeFlagsFormat.parse(Uint32Codec.readValue(in));
        Long size = flags.sizeAttributePresent ? Uint64Codec.readValue(in) : null;
        Integer uid = flags.userIdGroupIdAttributesPresent ? Uint32Codec.readValue(in) : null;
        Integer gid = flags.userIdGroupIdAttributesPresent ? Uint32Codec.readValue(in) : null;

        UnixFileType fileType = UnixFileType.REGULAR;
        PosixFilePermissions permissions = null;

        if (flags.permissionsAttributePresent) {
            int data = Uint32Codec.readValue(in);
            fileType = UnixFileType.parse(data);
            permissions = PosixFilePermissionsFormat.parse(data);
        }

        Integer atime = flags.accessModificationTimeAttributesPresent ? Uint32Codec.readValue(in) : null;
        Integer mtime = flags.accessModificationTimeAttributesPresent ? Uint32Codec.readValue(in) : null;
        Map<String, String> extendedData = flags.extendedAttributesPresent ? readExtendedData(in) : new HashMap<String, String>();

        return new FileAttributes(size, uid, gid, fileType, permissions, atime, mtime, extendedData);
    }

    public static void writeValue(FileAttributes value, OutputStream out)
            throws IOException {
        FileAttributeFlags flags = FileAttributeFlags.flagsFor(value);
        Uint32Codec.writeValue(FileAttributeFlagsFormat.format(flags), out);

        if (flags.sizeAttributePresent) {
            Uint64Codec.writeValue(value.size, out);
        }

        if (flags.userIdGroupIdAttributesPresent) {
            Uint32Codec.writeValue(value.userId != null ? value.userId : 0, out);
            Uint32Codec.writeValue(value.groupId != null ? value.groupId : 0, out);
        }

        if (flags.permissionsAttributePresent) {
            int data = PosixFilePermissionsFormat.format(value.permissions);

            if (value.fileType != null) {
                data = value.fileType.format(data);
            }

            Uint32Codec.writeValue(data, out);
        }

        if (flags.accessModificationTimeAttributesPresent) {
            Uint32Codec.writeValue(value.accessTime != null ? value.accessTime : 0, out);
            Uint32Codec.writeValue(value.modificationTime != null ? value.modificationTime : 0, out);
        }

        if (flags.extendedAttributesPresent) {
            writeExtendedData(value.extendedData, out);
        }
    }

    private static Map<String, String> readExtendedData(InputStream in)
            throws IOException {
        int count = Uint32Codec.readValue(in);
        Map<String, String> extendedData = new LinkedHashMap<>();
        while (count-- > 0) {
            String name = StringCodec.readUtf8Value(in);
            String data = StringCodec.readUtf8Value(in);
            extendedData.put(name, data);
        }

        return extendedData;
    }

    private static void writeExtendedData(Map<String, String> extendedData, OutputStream out)
            throws IOException {
        Uint32Codec.writeValue(extendedData.size(), out);
        for (Map.Entry<String, String> entry : extendedData.entrySet()) {
            StringCodec.writeUtf8Value(entry.getKey(), out);
            StringCodec.writeUtf8Value(entry.getValue(), out);
        }
    }

    private FileAttributesCodec() {
    }
}
