package metainfo

// this file is from https://github.com/majestrate/XD

import (
	"crypto/sha1"
	"io"
	"os"
	"path/filepath"

	"github.com/zeebo/bencode"
)

// FilePath type
type FilePath []string

// FilePath : get filepath
func (f FilePath) FilePath() string {
	return filepath.Join(f...)
}

// Open : open file using base path
func (f FilePath) Open(base string) (*os.File, error) {
	return os.OpenFile(filepath.Join(base, f.FilePath()), os.O_RDWR|os.O_CREATE, 0600)
}

// FileInfo struct
type FileInfo struct {
	// length of file
	Length uint64 `bencode:"length"`
	// relative path of file
	Path FilePath `bencode:"path"`
	// md5sum
	Sum []byte `bencode:"md5sum,omitempty"`
}

// Info : info section of torrent file
type Info struct {
	// length of pices in bytes
	PieceLength uint32 `bencode:"piece length"`
	// piece data
	Pieces []byte `bencode:"pieces"`
	// name of root file
	Path string `bencode:"name"`
	// file metadata
	Files []FileInfo `bencode:"files,omitempty"`
	// private torrent
	Private *int64 `bencode:"private,omitempty"`
	// length of file in signle file mode
	Length uint64 `bencode:"length,omitempty"`
	// md5sum
	Sum []byte `bencode:"md5sum,omitempty"`
}

// GetFiles : get fileinfos from this info section
func (i Info) GetFiles() (infos []FileInfo) {
	if i.Length > 0 {
		infos = append(infos, FileInfo{
			Length: i.Length,
			Path:   FilePath([]string{i.Path}),
			Sum:    i.Sum,
		})
	} else {
		infos = append(infos, i.Files...)
	}
	return
}

// NumPieces : length of Info
func (i Info) NumPieces() uint32 {
	return uint32(len(i.Pieces) / 20)
}

// TorrentFile : a torrent file
type TorrentFile struct {
	Info         Info       `bencode:"info"`
	Announce     string     `bencode:"announce"`
	AnnounceList [][]string `bencode:"announce-list"`
	Created      uint64     `bencode:"created"`
	Comment      []byte     `bencode:"comment"`
	CreatedBy    []byte     `bencode:"created by"`
	Encoding     []byte     `bencode:"encoding"`
}

// TotalSize : get total size of files from torrent info section
func (tf *TorrentFile) TotalSize() uint64 {
	if tf.IsSingleFile() {
		return tf.Info.Length
	}
	total := uint64(0)
	for _, f := range tf.Info.Files {
		total += f.Length
	}
	return total
}

// GetAllAnnounceURLS : get all trackers url
func (tf *TorrentFile) GetAllAnnounceURLS() (l []string) {
	l = make([]string, 0, 64)
	if len(tf.Announce) > 0 {
		l = append(l, tf.Announce)
	}
	for _, al := range tf.AnnounceList {
		for _, a := range al {
			if len(a) > 0 {
				l = append(l, a)
			}
		}
	}
	return
}

// TorrentName : return torrent name
func (tf *TorrentFile) TorrentName() string {
	return tf.Info.Path
}

// IsPrivate : return true if this torrent is private otherwise return false
func (tf *TorrentFile) IsPrivate() bool {
	return tf.Info.Private != nil && *tf.Info.Private == 1
}

// Infohash : calculate infohash
func (tf *TorrentFile) Infohash() (ih [20]byte, err error) {
	s := sha1.New()
	enc := bencode.NewEncoder(s)
	err = enc.Encode(&tf.Info)
	if err != nil {
		return
	}
	d := s.Sum(nil)
	copy(ih[:], d[:])
	return
}

// IsSingleFile : return true if this torrent is for a single file
func (tf *TorrentFile) IsSingleFile() bool {
	return tf.Info.Length > 0
}

// Encode : bencode this file via an io.Writer
func (tf *TorrentFile) Encode(w io.Writer) (err error) {
	enc := bencode.NewEncoder(w)
	err = enc.Encode(tf)
	return
}

// Decode : load from an io.Reader
func (tf *TorrentFile) Decode(r io.Reader) (err error) {
	dec := bencode.NewDecoder(r)
	err = dec.Decode(tf)
	return
}
