package main
import (
"bufio"
"fmt"
"io"
"net"
"net/http"
"net/url"
"regexp"
"os"
"os/signal"
"sort"
"strings"
"sync"
)
type WordAndCount struct {
word string
count uint
}
func newWordsAndCount(w *map[string]uint) []WordAndCount {
ret := make([]WordAndCount, len(*w))
i := 0
for word, count := range *w {
ret[i] = WordAndCount{word,count}
i++
}
return ret
}
func (wac WordAndCount) lessThan(other WordAndCount) bool {
return wac.count < other.count
}
type Model struct {
lock sync.RWMutex
words map[string]uint
}
func NewModel() Model {
return Model{words: make(map[string]uint)}
}
func (m *Model) AddWords(words []string) {
m.lock.Lock()
defer m.lock.Unlock()
for _, word := range words {
v, _ := m.words[word]
m.words[word] = v + 1
}
}
func (m *Model) GetMostCommonPairs(how_many uint) []WordAndCount {
m.lock.RLock()
defer m.lock.RUnlock()
wac := newWordsAndCount(&m.words)
sort.SliceStable(wac, func(i,j int) bool {
return ! wac[i].lessThan(wac[j])
})
if how_many >= uint(len(wac)) {
how_many = uint(len(wac))
}
return wac[:how_many]
}
type View struct{}
func NewView() View {
return View{}
}
func (v View) WordsFromInput(input string) []string {
return strings.Fields(input)
}
func (v View) TableFromRankedPairs(ranked_pairs []WordAndCount) string {
var b strings.Builder
for _, p := range ranked_pairs {
fmt.Fprintf(&b, "%-15s %3d\n", p.word,p.count)
}
return b.String()
}
func NewHttpController(m Model, v View) *http.ServeMux {
controller := http.NewServeMux()
controller.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
if req.Method == "GET" {
uri, _ := url.Parse(req.RequestURI)
q := uri.Query()
n, _ := q["n"]
var how_many uint
if len(n) > 0 {
fmt.Sscanf(n[0], "%d", &how_many)
}
if how_many == 0 {
how_many = 10
}
most_common_pairs := m.GetMostCommonPairs(how_many)
table := v.TableFromRankedPairs(most_common_pairs)
strings.NewReader(table + "\n").WriteTo(w)
} else if req.Method == "POST" {
var body strings.Builder
io.Copy(&body, req.Body)
words := v.WordsFromInput(body.String())
m.AddWords(words)
}
})
return controller
}
type LineController struct {m Model; v View}
func NewLineController(m Model, v View) LineController {
return LineController{m,v}
}
func (l *LineController) OnCommand(w io.Writer, command string, args string) {
if command == "get" {
l.getWords(w,args)
} else if command == "put" {
l.putWords(w,args)
} else {
l.reply(w,"bad command, only 'get' and 'put'")
}
}
func (l *LineController) reply(w io.Writer, text string) {
strings.NewReader(text + "\n").WriteTo(w)
}
func (l *LineController) getWords(w io.Writer, args string) {
var how_many uint
fmt.Sscanf(args, "%d", &how_many)
if how_many == 0 {
how_many = 10
}
most_common_pairs := l.m.GetMostCommonPairs(how_many)
table := l.v.TableFromRankedPairs(most_common_pairs)
l.reply(w,table)
}
func (l *LineController) putWords(w io.Writer, args string) {
words := l.v.WordsFromInput(args)
l.m.AddWords(words)
l.reply(w,"ok")
}
type TextServer struct {
c LineController
l net.Listener
}
func TextListenAndServe(host string, controller LineController) error {
l, err := net.Listen("tcp",host)
if err != nil {
return err
}
ts := TextServer{controller,l}
ts.Serve()
return nil
}
func (ts *TextServer) Serve() {
for conn,err := ts.l.Accept(); err == nil; conn,err = ts.l.Accept() {
go ts.serveOne(conn)
}
}
func (ts *TextServer) serveOne(conn net.Conn) {
split := regexp.MustCompile(`\s+`)
s := bufio.NewScanner(conn)
for s.Scan() {
parts := split.Split(s.Text(),2)
if len(parts)>1 {
ts.c.OnCommand(conn,parts[0],parts[1])
} else if len(parts)==1 {
ts.c.OnCommand(conn,parts[0],"")
}
}
conn.Close()
return
}
func main() {
model := NewModel()
view := NewView()
httpcontroller := NewHttpController(model, view)
go http.ListenAndServe("localhost:8080", httpcontroller)
linecontroller := NewLineController(model,view)
go TextListenAndServe("localhost:2020",linecontroller)
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, os.Interrupt)
<-sigs
}