/*
 * Copyright (C) 2005-2007 Alfresco Software Limited.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

 * As a special exception to the terms and conditions of version 2.0 of 
 * the GPL, you may redistribute this Program in connection with Free/Libre 
 * and Open Source Software ("FLOSS") applications as described in Alfresco's 
 * FLOSS exception.  You should have recieved a copy of the text describing 
 * the FLOSS exception, and it is also available here: 
 * http://www.alfresco.com/legal/licensing"
 */
package org.alfresco.filesys.smb.server;

import org.alfresco.filesys.server.filesys.FileInfo;
import org.alfresco.filesys.server.filesys.UnsupportedInfoLevelException;
import org.alfresco.filesys.smb.FileInfoLevel;
import org.alfresco.filesys.smb.NTTime;
import org.alfresco.filesys.smb.SMBDate;
import org.alfresco.filesys.smb.WinNT;
import org.alfresco.filesys.smb.server.ntfs.StreamInfo;
import org.alfresco.filesys.smb.server.ntfs.StreamInfoList;
import org.alfresco.filesys.util.DataBuffer;

/**
 * Query File Information Packer Class
 * <p>
 * Packs file/directory information for the specified information level.
 */
public class QueryInfoPacker
{

    /**
     * Pack a file information object into the specified buffer, using the specified information
     * level.
     * 
     * @param info File information to be packed.
     * @param buf Buffer to pack the data into.
     * @param infoLevel File information level.
     * @param uni Pack Unicode strings if true, else pack ASCII strings
     * @return int Length of data packed
     */
    public final static int packInfo(FileInfo info, DataBuffer buf, int infoLevel, boolean uni)
            throws UnsupportedInfoLevelException
    {

        // Determine the information level

        int curPos = buf.getPosition();

        switch (infoLevel)
        {

        // Standard information

        case FileInfoLevel.PathStandard:
            packInfoStandard(info, buf, false, uni);
            break;

        // Standard information plus EA size

        case FileInfoLevel.PathQueryEASize:
            packInfoStandard(info, buf, true, uni);
            break;

        // Extended attributes list

        case FileInfoLevel.PathQueryEAsFromList:
            break;

        // All extended attributes

        case FileInfoLevel.PathAllEAs:
            break;

        // Validate a file name

        case FileInfoLevel.PathIsNameValid:
            break;

        // Basic file information

        case FileInfoLevel.PathFileBasicInfo:
        case FileInfoLevel.NTFileBasicInfo:
            packBasicFileInfo(info, buf);
            break;

        // Standard file information

        case FileInfoLevel.PathFileStandardInfo:
        case FileInfoLevel.NTFileStandardInfo:
            packStandardFileInfo(info, buf);
            break;

        // Extended attribute information

        case FileInfoLevel.PathFileEAInfo:
        case FileInfoLevel.NTFileEAInfo:
            packEAFileInfo(info, buf);
            break;

        // File name information

        case FileInfoLevel.PathFileNameInfo:
        case FileInfoLevel.NTFileNameInfo:
            packNameFileInfo(info, buf, uni);
            break;

        // All information

        case FileInfoLevel.PathFileAllInfo:
        case FileInfoLevel.NTFileAllInfo:
            packAllFileInfo(info, buf, uni);
            break;

        // Alternate name information

        case FileInfoLevel.PathFileAltNameInfo:
        case FileInfoLevel.NTFileAltNameInfo:
            packAlternateNameFileInfo(info, buf);
            break;

        // Stream information

        case FileInfoLevel.PathFileStreamInfo:
        case FileInfoLevel.NTFileStreamInfo:
            packStreamFileInfo(info, buf, uni);
            break;

        // Compression information

        case FileInfoLevel.PathFileCompressionInfo:
        case FileInfoLevel.NTFileCompressionInfo:
            packCompressionFileInfo(info, buf);
            break;

        // File internal information

        case FileInfoLevel.NTFileInternalInfo:
            packFileInternalInfo(info, buf);
            break;

        // File position information

        case FileInfoLevel.NTFilePositionInfo:
            packFilePositionInfo(info, buf);
            break;

        // Attribute tag information

        case FileInfoLevel.NTAttributeTagInfo:
            packFileAttributeTagInfo(info, buf);
            break;

        // Network open information

        case FileInfoLevel.NTNetworkOpenInfo:
            packFileNetworkOpenInfo(info, buf);
            break;
        }

        // Return the length of the data that was packed

        return buf.getPosition() - curPos;
    }

    /**
     * Pack the standard file information
     * 
     * @param info File information
     * @param buf Buffer to pack data into
     * @param eaFlag Return EA size
     * @param uni Pack unicode strings
     */
    private static void packInfoStandard(FileInfo info, DataBuffer buf, boolean eaFlag, boolean uni)
    {

        // Information format :-
        // SMB_DATE CreationDate
        // SMB_TIME CreationTime
        // SMB_DATE LastAccessDate
        // SMB_TIME LastAccessTime
        // SMB_DATE LastWriteDate
        // SMB_TIME LastWriteTime
        // ULONG File size
        // ULONG Allocation size
        // USHORT File attributes
        // [ ULONG EA size ]

        // Pack the creation date/time

        SMBDate dateTime = new SMBDate(0);

        if (info.hasCreationDateTime())
        {
            dateTime.setTime(info.getCreationDateTime());
            buf.putShort(dateTime.asSMBDate());
            buf.putShort(dateTime.asSMBTime());
        }
        else
            buf.putZeros(4);

        // Pack the last access date/time

        if (info.hasAccessDateTime())
        {
            dateTime.setTime(info.getAccessDateTime());
            buf.putShort(dateTime.asSMBDate());
            buf.putShort(dateTime.asSMBTime());
        }
        else
            buf.putZeros(4);

        // Pack the last write date/time

        if (info.hasModifyDateTime())
        {
            dateTime.setTime(info.getModifyDateTime());
            buf.putShort(dateTime.asSMBDate());
            buf.putShort(dateTime.asSMBTime());
        }
        else
            buf.putZeros(4);

        // Pack the file size and allocation size

        buf.putInt(info.getSizeInt());

        if (info.getAllocationSize() < info.getSize())
            buf.putInt(info.getSizeInt());
        else
            buf.putInt(info.getAllocationSizeInt());

        // Pack the file attributes

        buf.putShort(info.getFileAttributes());

        // Pack the EA size, always zero

        if (eaFlag == true)
            buf.putZeros(4);
    }

    /**
     * Pack the basic file information (level 0x101)
     * 
     * @param info File information
     * @param buf Buffer to pack data into
     */
    private static void packBasicFileInfo(FileInfo info, DataBuffer buf)
    {

        // Information format :-
        // LARGE_INTEGER Creation date/time
        // LARGE_INTEGER Access date/time
        // LARGE_INTEGER Write date/time
        // LARGE_INTEGER Change date/time
        // UINT Attributes
        // UINT Unknown

        // Pack the creation date/time

        if (info.hasCreationDateTime())
        {
            buf.putLong(NTTime.toNTTime(info.getCreationDateTime()));
        }
        else
            buf.putZeros(8);

        // Pack the last access date/time

        if (info.hasAccessDateTime())
        {
            buf.putLong(NTTime.toNTTime(info.getAccessDateTime()));
        }
        else
            buf.putZeros(8);

        // Pack the last write and change date/time

        if (info.hasModifyDateTime())
        {
            long ntTime = NTTime.toNTTime(info.getModifyDateTime());
            buf.putLong(ntTime);
            buf.putLong(ntTime);
        }
        else
            buf.putZeros(16);

        // Pack the file attributes

        buf.putInt(info.getFileAttributes());

        // Pack unknown value

        buf.putZeros(4);
    }

    /**
     * Pack the standard file information (level 0x102)
     * 
     * @param info File information
     * @param buf Buffer to pack data into
     */
    private static void packStandardFileInfo(FileInfo info, DataBuffer buf)
    {

        // Information format :-
        // LARGE_INTEGER AllocationSize
        // LARGE_INTEGER EndOfFile
        // UINT NumberOfLinks
        // BOOLEAN DeletePending
        // BOOLEAN Directory
        // SHORT Unknown

        // Pack the allocation and file sizes

        if (info.getAllocationSize() < info.getSize())
            buf.putLong(info.getSize());
        else
            buf.putLong(info.getAllocationSize());

        buf.putLong(info.getSize());

        // Pack the number of links, always one for now

        buf.putInt(1);

        // Pack the delete pending and directory flags

        buf.putByte(0);
        buf.putByte(info.isDirectory() ? 1 : 0);

        // buf.putZeros(2);
    }

    /**
     * Pack the extended attribute information (level 0x103)
     * 
     * @param info File information
     * @param buf Buffer to pack data into
     */
    private static void packEAFileInfo(FileInfo info, DataBuffer buf)
    {

        // Information format :-
        // ULONG EASize

        // Pack the extended attribute size

        buf.putInt(0);
    }

    /**
     * Pack the file name information (level 0x104)
     * 
     * @param info File information
     * @param buf Buffer to pack data into
     * @param uni Pack unicode strings
     */
    private static void packNameFileInfo(FileInfo info, DataBuffer buf, boolean uni)
    {

        // Information format :-
        // UINT FileNameLength
        // WCHAR FileName[]

        // Pack the file name length and name string as Unicode

        int nameLen = info.getFileName().length();
        if (uni)
            nameLen *= 2;

        buf.putInt(nameLen);
        buf.putString(info.getFileName(), uni, false);
    }

    /**
     * Pack the all file information (level 0x107)
     * 
     * @param info File information
     * @param buf Buffer to pack data into
     * @param uni Pack unicode strings
     */
    private static void packAllFileInfo(FileInfo info, DataBuffer buf, boolean uni)
    {

        // Information format :-
        // LARGE_INTEGER Creation date/time
        // LARGE_INTEGER Access date/time
        // LARGE_INTEGER Write date/time
        // LARGE_INTEGER Change date/time
        // UINT Attributes
        // UINT Number of links
        // LARGE_INTEGER Allocation
        // LARGE_INTEGER Size
        // BYTE Delete pending
        // BYTE Directory flag
        // 2 byte longword alignment
        // UINT EA Size
        // UINT Access mask
        // UINT File name length
        // WCHAR FileName[]

        // Pack the creation date/time

        if (info.hasCreationDateTime())
        {
            buf.putLong(NTTime.toNTTime(info.getCreationDateTime()));
        }
        else
            buf.putZeros(8);

        // Pack the last access date/time

        if (info.hasAccessDateTime())
        {
            buf.putLong(NTTime.toNTTime(info.getAccessDateTime()));
        }
        else
            buf.putZeros(8);

        // Pack the last write and change date/time

        if (info.hasModifyDateTime())
        {
            long ntTime = NTTime.toNTTime(info.getModifyDateTime());
            buf.putLong(ntTime);
            buf.putLong(ntTime);
        }
        else
            buf.putZeros(16);

        // Pack the file attributes

        buf.putInt(info.getFileAttributes());

        // Number of links

        buf.putInt(1);

        // Pack the allocation and used file sizes

        if (info.getAllocationSize() < info.getSize())
            buf.putLong(info.getSize());
        else
            buf.putLong(info.getAllocationSize());

        buf.putLong(info.getSize());

        // Pack the delete pending and directory flags

        buf.putByte(0);
        buf.putByte(info.isDirectory() ? 1 : 0);
        buf.putShort(0); // Alignment

        // EA list size

        buf.putInt(0);

        // Access mask

        buf.putInt(0x00000003);

        // File name length in bytes and file name, Unicode

        int nameLen = info.getFileName().length();
        if (uni)
            nameLen *= 2;

        buf.putInt(nameLen);
        buf.putString(info.getFileName(), uni, false);
    }

    /**
     * Pack the alternate name information (level 0x108)
     * 
     * @param info File information
     * @param buf Buffer to pack data into
     */
    private static void packAlternateNameFileInfo(FileInfo info, DataBuffer buf)
    {
    }

    /**
     * Pack the stream information (level 0x109)
     * 
     * @param info File information
     * @param buf Buffer to pack data into
     * @param uni Pack unicode strings
     */
    private static void packStreamFileInfo(FileInfo info, DataBuffer buf, boolean uni)
    {

        // Information format :-
        // ULONG OffsetToNextStreamInfo
        // ULONG NameLength (in bytes)
        // LARGE_INTEGER StreamSize
        // LARGE_INTEGER StreamAlloc
        // WCHAR StreamName[]

        // Pack a dummy data stream for now

        String streamName = "::$DATA";

        buf.putInt(0); // offset to next info (no more info)

        int nameLen = streamName.length();
        if (uni)
            nameLen *= 2;
        buf.putInt(nameLen);

        // Stream size

        buf.putLong(info.getSize());

        // Allocation size

        if (info.getAllocationSize() < info.getSize())
            buf.putLong(info.getSize());
        else
            buf.putLong(info.getAllocationSize());

        buf.putString(streamName, uni, false);
    }

    /**
     * Pack the stream information (level 0x109)
     * 
     * @param streams List of streams
     * @param buf Buffer to pack data into
     * @param uni Pack unicode strings
     * @return int
     */
    public static int packStreamFileInfo(StreamInfoList streams, DataBuffer buf, boolean uni)
    {

        // Information format :-
        // ULONG OffsetToNextStreamInfo
        // ULONG NameLength (in bytes)
        // LARGE_INTEGER StreamSize
        // LARGE_INTEGER StreamAlloc
        // WCHAR StreamName[]

        // Loop through the available streams

        int curPos = buf.getPosition();
        int startPos = curPos;
        int pos = 0;

        for (int i = 0; i < streams.numberOfStreams(); i++)
        {

            // Get the current stream information

            StreamInfo sinfo = streams.getStreamAt(i);

            // Skip the offset to the next stream information structure

            buf.putInt(0);

            // Set the stream name length

            int nameLen = sinfo.getName().length();
            if (uni)
                nameLen *= 2;
            buf.putInt(nameLen);

            // Stream size

            buf.putLong(sinfo.getSize());

            // Allocation size

            if (sinfo.getAllocationSize() < sinfo.getSize())
                buf.putLong(sinfo.getSize());
            else
                buf.putLong(sinfo.getAllocationSize());

            buf.putString(sinfo.getName(), uni, false);

            // Word align the buffer

            buf.wordAlign();

            // Fill in the offset to the next stream information, if this is not the last stream

            if (i < (streams.numberOfStreams() - 1))
            {

                // Fill in the offset from the current stream information structure to the next

                pos = buf.getPosition();
                buf.setPosition(startPos);
                buf.putInt(pos - startPos);
                buf.setPosition(pos);
                startPos = pos;
            }
        }

        // Return the data length

        return buf.getPosition() - curPos;
    }

    /**
     * Pack the compression information (level 0x10B)
     * 
     * @param info File information
     * @param buf Buffer to pack data into
     */
    private static void packCompressionFileInfo(FileInfo info, DataBuffer buf)
    {

        // Information format :-
        // LARGE_INTEGER CompressedSize
        // ULONG CompressionFormat (sess WinNT class)

        buf.putLong(info.getSize());
        buf.putInt(WinNT.CompressionFormatNone);
    }

    /**
     * Pack the file internal information (level 1006)
     * 
     * @param info File information
     * @param buf Buffer to pack data into
     */
    private static void packFileInternalInfo(FileInfo info, DataBuffer buf)
    {

        // Information format :-
        // ULONG Unknown1
        // ULONG Unknown2

        buf.putInt(1);
        buf.putInt(0);
    }

    /**
     * Pack the file position information (level 1014)
     * 
     * @param info File information
     * @param buf Buffer to pack data into
     */
    private static void packFilePositionInfo(FileInfo info, DataBuffer buf)
    {

        // Information format :-
        // ULONG Unknown1
        // ULONG Unknown2

        buf.putInt(0);
        buf.putInt(0);
    }

    /**
     * Pack the network open information (level 1034)
     * 
     * @param info File information
     * @param buf Buffer to pack data into
     */
    private static void packFileNetworkOpenInfo(FileInfo info, DataBuffer buf)
    {

        // Information format :-
        // LARGE_INTEGER Creation date/time
        // LARGE_INTEGER Access date/time
        // LARGE_INTEGER Write date/time
        // LARGE_INTEGER Change date/time
        // LARGE_INTEGER Allocation
        // LARGE_INTEGER Size
        // UINT Attributes
        // UNIT Unknown

        // Pack the creation date/time

        if (info.hasCreationDateTime())
        {
            buf.putLong(NTTime.toNTTime(info.getCreationDateTime()));
        }
        else
            buf.putZeros(8);

        // Pack the last access date/time

        if (info.hasAccessDateTime())
        {
            buf.putLong(NTTime.toNTTime(info.getAccessDateTime()));
        }
        else
            buf.putZeros(8);

        // Pack the last write and change date/time

        if (info.hasModifyDateTime())
        {
            long ntTime = NTTime.toNTTime(info.getModifyDateTime());
            buf.putLong(ntTime);
            buf.putLong(ntTime);
        }
        else
            buf.putZeros(16);

        // Pack the allocation and used file sizes

        if (info.getAllocationSize() < info.getSize())
            buf.putLong(info.getSize());
        else
            buf.putLong(info.getAllocationSize());

        buf.putLong(info.getSize());

        // Pack the file attributes

        buf.putInt(info.getFileAttributes());

        // Pack the unknown value

        buf.putInt(0);
    }

    /**
     * Pack the attribute tag information (level 1035)
     * 
     * @param info File information
     * @param buf Buffer to pack data into
     */
    private static void packFileAttributeTagInfo(FileInfo info, DataBuffer buf)
    {

        // Information format :-
        // ULONG Unknown1
        // ULONG Unknown2

        buf.putLong(0);
        buf.putLong(0);
    }
}
