package main

// stdlib imports
import "os"
import "path"
import "log"
import "fmt"
import "regexp"
import "strings"
import "sort"

// 3rd-party imports
import "notmuch"
import "github.com/jteeuwen/go-pkg-ini/ini"

type mail_addr_freq struct {
	addr  string
	count [3]uint
}

type frequencies map[string]uint

/* Used to sort the email addresses from most to least used */
func sort_by_freq(m1, m2 *mail_addr_freq) int {
	if (m1.count[0] == m2.count[0] &&
		m1.count[1] == m2.count[1] &&
		m1.count[2] == m2.count[2]) {
		return 0
	}

	if (m1.count[0] >  m2.count[0] ||
		m1.count[0] == m2.count[0] &&
		m1.count[1] >  m2.count[1] ||
		m1.count[0] == m2.count[0] &&
		m1.count[1] == m2.count[1] &&
		m1.count[2] >  m2.count[2]) {
		return -1
	}

	return 1
}

type maddresses []*mail_addr_freq

func (self *maddresses) Len() int {
	return len(*self)
}

func (self *maddresses) Less(i,j int) bool {
	m1 := (*self)[i]
	m2 := (*self)[j]
	v  := sort_by_freq(m1, m2)
	if v<=0 {
		return true
	}
	return false
}

func (self *maddresses) Swap(i,j int) {
	(*self)[i], (*self)[j] = (*self)[j], (*self)[i]
}

// find most frequent real name for each mail address
func frequent_fullname(freqs frequencies) string {
	var maxfreq uint = 0
	fullname := ""
	freqs_sz := len(freqs)

	for mail,freq := range freqs {
		if (freq > maxfreq && mail != "") || freqs_sz == 1 {
			// only use the entry if it has a real name
			// or if this is the only entry
			maxfreq = freq
			fullname = mail
		}
	}
	return fullname
}

func addresses_by_frequency(msgs *notmuch.Messages, name string, pass uint, addr_to_realname *map[string]*frequencies) *frequencies {

	freqs := make(frequencies)

	pattern := `\s*(("(\.|[^"])*"|[^,])*<?(?mail\b\w+([-+.]\w+)*\@\w+[-\.\w]*\.([-\.\w]+)*\w\b)>?)`
	// pattern := "\\s*((\\\"(\\\\.|[^\\\\\"])*\\\"|[^,])*" +
	// 	"<?(?P<mail>\\b\\w+([-+.]\\w+)*\\@\\w+[-\\.\\w]*\\.([-\\.\\w]+)*\\w\\b)>?)"
	pattern = `.*` + strings.ToLower(name) + `.*`
	var re *regexp.Regexp = nil
	var err os.Error = nil
	if re,err = regexp.Compile(pattern); err != nil {
		log.Printf("error: %v\n", err)
		return &freqs
	}
	
	headers := []string{"from"}
	if pass == 1 {
		headers = append(headers, "to", "cc", "bcc")
	}

	for ;msgs.Valid();msgs.MoveToNext() {
		msg := msgs.Get()
		//println("==> msg [", msg.GetMessageId(), "]")
		for _,header := range headers {
			froms := strings.ToLower(msg.GetHeader(header))
			//println("  froms: ["+froms+"]")
			for _,from := range strings.Split(froms, ",", -1) {
				from = strings.Trim(from, " ")
				match := re.FindString(from)
				//println("  -> match: ["+match+"]")
				occ,ok := freqs[match]
				if !ok {
					freqs[match] = 0
					occ = 0
				}
				freqs[match] = occ+1
			}
		}
	}
	return &freqs
}

func search_address_passes(queries [3]*notmuch.Query, name string) []string {
	var val []string
	addr_freq := make(map[string]*mail_addr_freq)
	addr_to_realname := make(map[string]*frequencies)

	var pass uint = 0 // 0-based
	for _,query := range queries {
		if query == nil {
			//println("**warning: idx [",idx,"] contains a nil query")
			continue
		}
		msgs := query.SearchMessages()
		ht := addresses_by_frequency(msgs, name, pass, &addr_to_realname)
		for addr, count := range *ht {
			freq,ok := addr_freq[addr]
			if !ok {
				freq = &mail_addr_freq{addr:addr, count:[3]uint{0,0,0}}
			}
			freq.count[pass] = count
			addr_freq[addr] = freq
		}
		msgs.Destroy()
		pass += 1
	}

	addrs := make(maddresses, len(addr_freq))
	{
		iaddr := 0
		for _, freq := range addr_freq {
			addrs[iaddr] = freq
			iaddr += 1
		}
	}
	sort.Sort(&addrs)

	for _,addr := range addrs {
		freqs,ok := addr_to_realname[addr.addr]
		if ok {
			val = append(val, frequent_fullname(*freqs))
		} else {
			val = append(val, addr.addr)
		}
	}
	//println("val:",val)
	return val
}

type address_matcher struct {
	// the notmuch database
	db *notmuch.Database
	// full path of the notmuch database
	user_db_path string
	// user primary email
	user_primary_email string
	// user tag to mark from addresses as in the address book
	user_addrbook_tag string
}

func new_address_matcher() *address_matcher {
	var cfg *ini.Config
	var err os.Error

	// honor NOTMUCH_CONFIG
	home := os.Getenv("NOTMUCH_CONFIG")
	if home == "" {
		home = os.Getenv("HOME")
	}

	if cfg,err = ini.Load(path.Join(home, ".notmuch-config")); err != nil {
		log.Exitf("error loading config file:",err)
	}

	db_path := cfg.S("database", "path", "")
	primary_email := cfg.S("user", "primary_email", "")
	addrbook_tag := cfg.S("user", "addrbook_tag", "addressbook")

	self := &address_matcher{db:nil, 
	                         user_db_path:db_path,
	                         user_primary_email:primary_email,
	                         user_addrbook_tag:addrbook_tag}
	return self
}

func (self *address_matcher) run(name string) {
	queries := [3]*notmuch.Query{}
	
	// open the database
	self.db = notmuch.OpenDatabase(self.user_db_path, 
		notmuch.DATABASE_MODE_READ_ONLY)

	// pass 1: look at all from: addresses with the address book tag
	query := "tag:" + self.user_addrbook_tag
	if name != "" {
		query = query + " and from:" + name + "*"
	}
	queries[0] = self.db.CreateQuery(query)

	// pass 2: look at all to: addresses sent from our primary mail
	query = ""
	if name != "" {
		query = "to:"+name+"*"
	}
	if self.user_primary_email != "" {
		query = query + " from:" + self.user_primary_email
	}
	queries[1] = self.db.CreateQuery(query)

	// if that leads only to a few hits, we check every from too
	if queries[0].CountMessages() + queries[1].CountMessages() < 10 {
		query = ""
		if name != "" {
			query = "from:"+name+"*"
		}
		queries[2] = self.db.CreateQuery(query)
	}
	
	// actually retrieve and sort addresses
	results := search_address_passes(queries, name)
	for _,v := range results {
		if v != "" && v != "\n" {
			fmt.Println(v)
		}
	}
	return
}

func main() {
	//fmt.Println("args:",os.Args)
	app := new_address_matcher()
	name := ""
	if len(os.Args) > 1 {
		name = os.Args[1]
	}
	app.run(name)
}