package main
import (
"bufio"
"fmt"
"io"
"net"
"net/http"
"net/url"
"regexp"
"os"
"os/signal"
"sort"
"strings"
"sync"
)
type wordsByCount struct {
words *map[string]uint
keys []string
}
func newWordsByCount(w *map[string]uint) wordsByCount {
keys := make([]string, len(*w))
i := 0
for w, _ := range *w {
keys[i] = w
i++
}
return wordsByCount{words: w, keys: keys}
}
func (wbc wordsByCount) Len() int { return len(*wbc.words) }
func (wbc wordsByCount) Swap(i, j int) {
wbc.keys[i], wbc.keys[j] = wbc.keys[j], wbc.keys[i]
}
func (wbc wordsByCount) Less(i, j int) bool {
return (*wbc.words)[wbc.keys[i]] > (*wbc.words)[wbc.keys[j]]
}
func (wbc wordsByCount) MostCommonWords() []string {
sort.Sort(wbc)
mcw := make([]string, len(*wbc.words))
for i, p := range wbc.keys {
mcw[i] = p
}
return mcw
}
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) []string {
m.lock.RLock()
defer m.lock.RUnlock()
wbc := newWordsByCount(&m.words)
mcw := wbc.MostCommonWords()
if how_many >= uint(len(mcw)) {
how_many = uint(len(mcw))
}
return mcw[: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 []string) string {
var b strings.Builder
for _, s := range ranked_pairs {
fmt.Fprintf(&b, "%s\n", s)
}
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
}