package main

import (
	"database/sql"
	"flag"
	"fmt"
	"log"
	"net/mail"
	"os"
	"path/filepath"
	"strings"

	_ "github.com/mattn/go-sqlite3"
)

var (
	quiet       = flag.Bool("q", false, "don't print status messages to stdout")
	debugLogFn  = flag.String("debugLogFn", "/dev/null", "file to store debugging logs")
	sentDirGlob = flag.String("sentGlob", "${HOME}/Maildir/.Sent/*/*",
		"Pattern for email sent files")
	addressDbFn = flag.String("addressDbFn", "${HOME}/.mutt/abook.db",
		"Location to store sqlite database of contacts")
)

type Contact struct {
	mail.Address
	fn string
}

func getAddresses(fn string, results chan<- *Contact) {
	file, err := os.Open(fn)
	if err != nil {
		log.Fatalf("Failed to open %q: %s\n", fn, err)
	}

	msg, err := mail.ReadMessage(file)
	if err != nil {
		log.Printf("Failed to parse message %q: %s\n", fn, err)
		return
	}

	headers := []string{"to", "cc", "resent-to", "resent-cc", "bcc"}
	for _, header := range headers {
		addrs, err := msg.Header.AddressList(header)
		if err != nil {
			//log.Printf("Failed to parse %s: header in %q: %s", header, fn, err)
			continue
		}

		for _, addr := range addrs {
			c := &Contact{*addr, fn}
			results <- c
		}
	}
}

func getEmails(results chan<- *Contact) {

	files, err := filepath.Glob(os.ExpandEnv(*sentDirGlob))
	if !(*quiet) {
		fmt.Printf("Scanning %d files\n", len(files))
	}

	if err != nil {
		log.Fatalf("Failed to glob %q: %s\n", *sentDirGlob, err)
	}

	for _, fn := range files {
		getAddresses(fn, results)
	}
	close(results)
}

func createEmailTable(db *sql.DB) {
	sqls := []string{
		"DROP TABLE IF EXISTS emails",
		"CREATE TABLE emails (email, name, fn)",
	}

	for _, sql := range sqls {
		_, err := db.Exec(sql)
		if err != nil {
			log.Fatalf("Error initializing DB %q: %s\n", err, sql)
		}
	}

}

func createSearchTable(db *sql.DB) {
	sqls := []string{
		"DROP TABLE IF EXISTS search",
		`CREATE TABLE search AS
		SELECT email, name, count(*) AS frequency
		FROM emails group BY name, email
		ORDER BY frequency`,
	}

	for _, sql := range sqls {
		_, err := db.Exec(sql)
		if err != nil {
			log.Fatalf("Error creating search table %q: %s\n", err, sql)
		}
	}
}

func fillEmailTable(db *sql.DB, results <-chan *Contact) {
	tx, err := db.Begin()
	if err != nil {
		log.Fatalf("Faild to being transaction: %s\n", err)
		return
	}
	defer tx.Commit()

	stmt, err := tx.Prepare("INSERT INTO emails VALUES (?, ?, ?)")
	defer stmt.Close()
	if err != nil {
		log.Fatalf("Error preparing statement: %s\n", err)
		return
	}

	count := 1
	for c := range results {
		addr := c.Address
		if !(*quiet) {
			if 0 == count%50 {
				fmt.Print(count)
			}

			if 0 == count%10 {
				fmt.Print(".")
			}
		}
		count += 1

		var name string
		if addr.Name != "" {
			name = addr.Name
			if -1 != strings.Index(name, ",") {
				parts := strings.SplitN(name, ",", 2)
				first, second := strings.TrimSpace(parts[1]), parts[0]
				name = fmt.Sprintf("%s %s", first, second)
			}
		} else {
			name = addr.Address
		}
		address := strings.ToLower(addr.Address)

		_, err = stmt.Exec(address, name, c.fn)
		if err != nil {
			log.Fatalf("Failed to insert value: %s\n", err)
		}
	}
}

func buildDb(results <-chan *Contact) {
	os.Remove(os.ExpandEnv(*addressDbFn))

	db, err := sql.Open("sqlite3", os.ExpandEnv(*addressDbFn))
	if err != nil {
		fmt.Println(err)
		return
	}
	defer db.Close()

	createEmailTable(db)
	fillEmailTable(db, results)
	createSearchTable(db)
}

func init() {
	flag.Parse()
}

func main() {
	logFile, err := os.OpenFile(*debugLogFn, os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		log.Fatalf("Failed to open log %q: %s\n", *debugLogFn, err)
	}
	defer logFile.Close()
	log.SetOutput(logFile)
	log.Println("Started")

	results := make(chan *Contact)

	go getEmails(results)

	buildDb(results)

	log.Println("Finished successfully")
}
