package changelist

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"sort"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/docker/distribution/uuid"
)

// FileChangelist stores all the changes as files
type FileChangelist struct {
	dir string
}

// NewFileChangelist is a convenience method for returning FileChangeLists
func NewFileChangelist(dir string) (*FileChangelist, error) {
	logrus.Debug("Making dir path: ", dir)
	err := os.MkdirAll(dir, 0700)
	if err != nil {
		return nil, err
	}
	return &FileChangelist{dir: dir}, nil
}

// List returns a list of sorted changes
func (cl FileChangelist) List() []Change {
	var changes []Change
	dir, err := os.Open(cl.dir)
	if err != nil {
		return changes
	}
	defer dir.Close()
	fileInfos, err := dir.Readdir(0)
	if err != nil {
		return changes
	}
	sort.Sort(fileChanges(fileInfos))
	for _, f := range fileInfos {
		if f.IsDir() {
			continue
		}
		raw, err := ioutil.ReadFile(path.Join(cl.dir, f.Name()))
		if err != nil {
			logrus.Warn(err.Error())
			continue
		}
		c := &TufChange{}
		err = json.Unmarshal(raw, c)
		if err != nil {
			logrus.Warn(err.Error())
			continue
		}
		changes = append(changes, c)
	}
	return changes
}

// Add adds a change to the file change list
func (cl FileChangelist) Add(c Change) error {
	cJSON, err := json.Marshal(c)
	if err != nil {
		return err
	}
	filename := fmt.Sprintf("%020d_%s.change", time.Now().UnixNano(), uuid.Generate())
	return ioutil.WriteFile(path.Join(cl.dir, filename), cJSON, 0644)
}

// Clear clears the change list
func (cl FileChangelist) Clear(archive string) error {
	dir, err := os.Open(cl.dir)
	if err != nil {
		return err
	}
	defer dir.Close()
	files, err := dir.Readdir(0)
	if err != nil {
		return err
	}
	for _, f := range files {
		os.Remove(path.Join(cl.dir, f.Name()))
	}
	return nil
}

// Close is a no-op
func (cl FileChangelist) Close() error {
	// Nothing to do here
	return nil
}

type fileChanges []os.FileInfo

// Len returns the length of a file change list
func (cs fileChanges) Len() int {
	return len(cs)
}

// Less compares the names of two different file changes
func (cs fileChanges) Less(i, j int) bool {
	return cs[i].Name() < cs[j].Name()
}

// Swap swaps the position of two file changes
func (cs fileChanges) Swap(i, j int) {
	tmp := cs[i]
	cs[i] = cs[j]
	cs[j] = tmp
}
