package smb

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"os"
	"path/filepath"
	"sync/atomic"

	"imuslab.com/smb/driver/mod/smb/encoder"
)

func init() {
	commandRequestMap[CommandQueryInfo] = func() DataI {
		return &QueryInfoRequest{}
	}
}

type QueryInfoRequest struct {
	Header
	StructureSize         uint16
	Class                 InfoType //Class
	InfoLevel             uint8    //FileSystemInformationClass or FileInformationClass
	OutputBufferLength    uint32   //max response size
	InputBufferOffset     uint16   `smb:"offset:InputBuffer"`
	Reserved              uint16
	InputBufferLength     uint32 `smb:"len:InputBuffer"`
	AdditionalInformation uint32
	Flags                 uint32
	FileId                GUID
	InputBuffer           []byte
}

type QueryInfoResponse struct {
	Header
	StructureSize      uint16
	OutputBufferOffset uint16 `smb:"offset:OutputBuffer"`
	OutputBufferLength uint32 `smb:"len:OutputBuffer"`
	OutputBuffer       []byte
}

func (data *QueryInfoRequest) ServerAction(ctx *DataCtx) (interface{}, error) {
	data.Header.Flags = SMB2_FLAGS_RESPONSE

	fileid := ctx.FileID(data.FileId)
	webfile, ok := ctx.session.openedFiles[fileid]
	if !ok {
		return ERR(data.Header, STATUS_FILE_CLOSED)
	}

	var OutputBuffer []byte
	var err error

	switch data.Class {
	case SMB2_0_INFO_FILE:
		switch FileInformationClass(data.InfoLevel) {
		case FileStreamInformation:
			ff := webfile.(*os.File)

			var infos [][]byte
			if true {
				fi, err := ff.Stat()
				if err != nil {
					return nil, err
				}
				datakey := "::$DATA"
				name := encoder.ToUnicode(datakey)
				info := &FileStreamInformationX{
					StreamSize:           uint64(fi.Size()),
					StreamAllocationSize: uint64(fi.Size()),
					StreamName:           name,
				}
				infobuf, err := encoder.Marshal(info)
				if err != nil {
					panic(-1)
				}

				infos = append(infos, Duiqi4Byte(infobuf))
			}

			keys, err := XAttrGetKeys(ff.Name())
			if err != nil {
				return nil, fmt.Errorf("err")
			}

			for _, key := range keys {
				datakey := fmt.Sprintf(":%v:$DATA", key)
				name := encoder.ToUnicode(datakey)

				data, err := XAttrGet(ff.Name(), key)
				if err != nil {
					continue
				}
				info := &FileStreamInformationX{
					StreamSize:           uint64(len(data)),
					StreamAllocationSize: uint64(len(data)),
					StreamName:           name,
				}
				infobuf, err := encoder.Marshal(info)
				if err != nil {
					continue
				}
				infos = append(infos, Duiqi4Byte(infobuf))
			}

			//write offset
			for i, info := range infos {
				if i != len(infos)-1 {
					binary.LittleEndian.PutUint32(info, uint32(len(info)))
				}
			}

			OutputBuffer = bytes.Join(infos, []byte{})

		case FileAllInformation:
			fi, _ := webfile.Stat()
			fname := filepath.Base(fi.Name())
			IsDirectory := 0
			fid := uint64(0)
			FileAttributes := uint32(0x000020)
			if fi.IsDir() {
				IsDirectory = 1
				FileAttributes = uint32(0x000010)
			} else {
				fid = atomic.AddUint64(&KFILEID, 1)
			}
			mtime := timeToFiletime(fi.ModTime())

			info := SMB2_FILE_ALL_INFO{
				CreateTime:     mtime,
				LastAccessTime: mtime,
				LastWriteTime:  mtime,
				LastChangeTime: mtime,
				AccessMask:     AllAccessMask,
				NumberOfLinks:  18,
				FileID:         fid,
				FileAttributes: FileAttributes,
				IsDirectory:    uint8(IsDirectory),
				FileName:       encoder.ToUnicode(fname),
			}

			OutputBuffer, err = encoder.Marshal(info)
			if err != nil {
				return ERR(data.Header, STATUS_INVALID_PARAMETER)
			}

			// logx.Printf("FileAllInformation: \n%v", hex.Dump(OutputBuffer))
		}
	case SMB2_0_INFO_FILESYSTEM:
		switch FileSystemInformationClass(data.InfoLevel) {
		case FileFsAttributeInformation:
			//TODO 获取FSInfo
			ntfs := encoder.ToUnicode("NTFS")
			info := FileFsAttributeInformationX{
				FSAttributes:  0x10400c6,
				MaxNameLength: 256,
				FSName:        ntfs,
			}
			OutputBuffer, err = encoder.Marshal(info)
			if err != nil {
				return ERR(data.Header, STATUS_INVALID_PARAMETER)
			}
		case FileFsSizeInformation:
			//TODO 计算磁盘剩余大小.
			info := FileFsSizeInformationX{
				AllocationSize: 0xfffffff,
				FreeUnits:      0xffffff,
				SectorsUnit:    1,
				BytesPerSector: 4096,
			}
			OutputBuffer, err = encoder.Marshal(info)
			if err != nil {
				return ERR(data.Header, STATUS_INVALID_PARAMETER)
			}
		}
	}

	data.Header.Status = StatusOk
	resp := QueryInfoResponse{
		Header:        data.Header,
		StructureSize: 9,
		OutputBuffer:  OutputBuffer,
	}
	return &resp, nil
}
