package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"xinu.tv/email"

	"github.com/golang/glog"
)

var (
	dryrun    = flag.Bool("dryrun", true, "don't actually rename files, just log actions.")
	saveFile  = flag.String("save", "", "filename to store unread status.")
	loadFile  = flag.String("load", "", "filename to load unread status.")
	maildir   = flag.String("maildir", "", "Maildir root")
	skipFiles = flag.String("skip", "maildirfolder,log,msgid.cache,razor-agent.log",
		"comma separated files to skip")
)

type Status struct {
	Hash string `json:"hash"`
	Path string `json:"path,omitempthashy"`
	Read bool   `json:"read,omitempty"`
}

type Messages struct {
	// Filenames to skip when hash mail.
	Skip map[string]struct{}
	// Read mail status.
	Statuses []Status
}

func (m *Messages) hashMail(path string, info os.FileInfo, err error) error {
	glog.Infoln("Processing", path)
	if err != nil {
		return err
	}

	if info.IsDir() {
		return nil
	}

	base := filepath.Base(path)
	if _, ok := m.Skip[base]; ok {
		glog.Infoln("Skipping", path)
		return nil
	}

	r, err := os.Open(path)
	if err != nil {
		glog.Fatal(err)
	}
	defer r.Close()

	h, err := email.HashReader(r)
	if err != nil {
		glog.Errorf("%s not an mail file", path)
		glog.Infof("%q", err.Error())
		return nil
	}
	md := email.NewInfo(path)
	m.Statuses = append(m.Statuses, Status{
		Path: path,
		Hash: h,
		Read: md.Seen,
	})
	return nil
}

func (m *Messages) LoadStatus(statusFile string) error {
	glog.Infof("Loading file %q", statusFile)
	r, err := os.Open(statusFile)
	if err != nil {
		return nil
	}
	defer r.Close()
	dec := json.NewDecoder(r)
	return dec.Decode(&m.Statuses)
}

func (m *Messages) SaveStatus(maildir, statusFile string) error {
	if err := filepath.Walk(maildir, m.hashMail); err != nil {
		return err
	}

	b, err := json.MarshalIndent(m, "", "  ")
	if err != nil {
		return err
	}

	glog.Infof("Saving file %q", statusFile)
	f, err := os.Create(statusFile)
	if err != nil {
		return err
	}
	defer f.Close()

	n, err := f.Write(b)
	if err != nil {
		return err
	}
	if n != len(b) {
		return fmt.Errorf("Short write of save status: wrote %d of %d", n,
			len(b))
	}
	return nil
}

func (m Messages) Reconcile(maildir string) error {
	hashMap := make(map[string]*Status, len(m.Statuses))
	for i, msg := range m.Statuses {
		hashMap[msg.Hash] = &m.Statuses[i]
	}

	return filepath.Walk(maildir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if info.IsDir() {
			return nil
		}

		base := filepath.Base(path)
		if _, ok := m.Skip[base]; ok {
			glog.Infoln("Skipping", path)
			return nil
		}

		r, err := os.Open(path)
		if err != nil {
			glog.Fatal(err)
		}
		defer r.Close()

		chksum, err := email.HashReader(r)
		if err != nil {
			glog.Errorf("%s not an mail file", path)
			glog.Infof("%q", err.Error())
			return nil
		}

		md := email.NewInfo(path)
		s, ok := hashMap[chksum]
		if !ok {
			return nil
		}

		glog.V(2).Infof("Comparing flags of %q to %q", path, s.Path)
		if md.Seen != s.Read {
			md.Seen = s.Read
			newPath := md.String()
			glog.Infof("%s => %s", path, newPath)
			if !*dryrun {
				os.Rename(path, newPath)
			}
		}
		return nil
	})
}

func (m Messages) PrintStats() {
	r, u, t := 0, 0, 0
	for _, msg := range m.Statuses {
		t++
		if msg.Read {
			r++
		} else {
			u++
		}
	}
	fmt.Println(r, "read")
	fmt.Println(u, "unread")
	fmt.Println(t, "total")
}

func main() {
	defer glog.Flush()
	flag.Parse()
	skipStr := strings.Split(*skipFiles, ",")
	skip := make(map[string]struct{}, len(skipStr))
	for _, s := range skipStr {
		skip[s] = struct{}{}
	}
	glog.Infoln("Skip files", skip)

	m := Messages{
		Skip: skip,
	}

	if *maildir == "" {
		fmt.Println("Must specify Maildir with -maildir")
		os.Exit(1)
	}

	if *saveFile == "" && *loadFile == "" {
		fmt.Println("Must specify one of -save or -load")
		os.Exit(1)
	}

	if *saveFile != "" {
		if err := m.SaveStatus(*maildir, *saveFile); err != nil {
			glog.Fatal(err)
		}
		m.PrintStats()
	}

	if *loadFile != "" {
		if err := m.LoadStatus(*loadFile); err != nil {
			glog.Fatal(err)
		}
		m.PrintStats()
		if err := m.Reconcile(*maildir); err != nil {
			glog.Fatal(err)
		}
	}
}
