1package cowsay
2
3import (
4	"fmt"
5	"strings"
6
7	wordwrap "github.com/Code-Hex/go-wordwrap"
8	runewidth "github.com/mattn/go-runewidth"
9)
10
11type border struct {
12	first  [2]rune
13	middle [2]rune
14	last   [2]rune
15	only   [2]rune
16}
17
18func (cow *Cow) borderType() border {
19	if cow.thinking {
20		return border{
21			first:  [2]rune{'(', ')'},
22			middle: [2]rune{'(', ')'},
23			last:   [2]rune{'(', ')'},
24			only:   [2]rune{'(', ')'},
25		}
26	}
27
28	return border{
29		first:  [2]rune{'/', '\\'},
30		middle: [2]rune{'|', '|'},
31		last:   [2]rune{'\\', '/'},
32		only:   [2]rune{'<', '>'},
33	}
34}
35
36type line struct {
37	text      string
38	runeWidth int
39}
40
41type lines []*line
42
43func (cow *Cow) maxLineWidth(lines []*line) int {
44	maxWidth := 0
45	for _, line := range lines {
46		if line.runeWidth > maxWidth {
47			maxWidth = line.runeWidth
48		}
49		if !cow.disableWordWrap && maxWidth > cow.ballonWidth {
50			return cow.ballonWidth
51		}
52	}
53	return maxWidth
54}
55
56func (cow *Cow) getLines(phrase string) []*line {
57	text := cow.canonicalizePhrase(phrase)
58	lineTexts := strings.Split(text, "\n")
59	lines := make([]*line, 0, len(lineTexts))
60	for _, lineText := range lineTexts {
61		lines = append(lines, &line{
62			text:      lineText,
63			runeWidth: runewidth.StringWidth(lineText),
64		})
65	}
66	return lines
67}
68
69func (cow *Cow) canonicalizePhrase(phrase string) string {
70	// Replace tab to 8 spaces
71	phrase = strings.Replace(phrase, "\t", "       ", -1)
72
73	if cow.disableWordWrap {
74		return phrase
75	}
76	width := cow.ballonWidth
77	return wordwrap.WrapString(phrase, uint(width))
78}
79
80// Balloon to get the balloon and the string entered in the balloon.
81func (cow *Cow) Balloon(phrase string) string {
82	defer cow.buf.Reset()
83
84	lines := cow.getLines(phrase)
85	maxWidth := cow.maxLineWidth(lines)
86
87	cow.writeBallon(lines, maxWidth)
88
89	return cow.buf.String()
90}
91
92func (cow *Cow) writeBallon(lines []*line, maxWidth int) {
93	top := make([]byte, 0, maxWidth+2)
94	bottom := make([]byte, 0, maxWidth+2)
95
96	top = append(top, ' ')
97	bottom = append(bottom, ' ')
98
99	for i := 0; i < maxWidth+2; i++ {
100		top = append(top, '_')
101		bottom = append(bottom, '-')
102	}
103
104	borderType := cow.borderType()
105
106	cow.buf.Write(top)
107	cow.buf.Write([]byte{' ', '\n'})
108	defer func() {
109		cow.buf.Write(bottom)
110		cow.buf.Write([]byte{' ', '\n'})
111	}()
112
113	l := len(lines)
114	if l == 1 {
115		border := borderType.only
116		cow.buf.WriteRune(border[0])
117		cow.buf.WriteRune(' ')
118		cow.buf.WriteString(lines[0].text)
119		cow.buf.WriteRune(' ')
120		cow.buf.WriteRune(border[1])
121		cow.buf.WriteRune('\n')
122		return
123	}
124
125	var border [2]rune
126	for i := 0; i < l; i++ {
127		switch i {
128		case 0:
129			border = borderType.first
130		case l - 1:
131			border = borderType.last
132		default:
133			border = borderType.middle
134		}
135		cow.buf.WriteRune(border[0])
136		cow.buf.WriteRune(' ')
137		cow.padding(lines[i], maxWidth)
138		cow.buf.WriteRune(' ')
139		cow.buf.WriteRune(border[1])
140		cow.buf.WriteRune('\n')
141	}
142}
143
144func (cow *Cow) flush(text, top, bottom fmt.Stringer) string {
145	return fmt.Sprintf(
146		"%s\n%s%s\n",
147		top.String(),
148		text.String(),
149		bottom.String(),
150	)
151}
152
153func (cow *Cow) padding(line *line, maxWidth int) {
154	if maxWidth <= line.runeWidth {
155		cow.buf.WriteString(line.text)
156		return
157	}
158
159	cow.buf.WriteString(line.text)
160	l := maxWidth - line.runeWidth
161	for i := 0; i < l; i++ {
162		cow.buf.WriteRune(' ')
163	}
164}
165