package nucular

import (
	"fmt"
	"image"
	"image/color"
	"log"
	"math"
	"path/filepath"
	"runtime"
	"strings"
	"time"
	"unicode"
	"unicode/utf8"

	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/mouse"

	"golang.org/x/image/font"
	"golang.org/x/image/math/fixed"

	"github.com/renerocksai/mdstyle"
	"github.com/renerocksai/nucular/clipboard"
	"github.com/renerocksai/nucular/command"
	"github.com/renerocksai/nucular/label"
	"github.com/renerocksai/nucular/rect"
	nstyle "github.com/renerocksai/nucular/style"
	"github.com/renerocksai/nucular/themes"
)

const (
	tabSizeInSpaces = 8
)

type textWidget struct {
	Padding    image.Point
	Background color.RGBA
	Text       color.RGBA
}

func textClamp(f font.Face, text []rune, space int) []rune {
	text_width := 0
	for i, ch := range text {
		_, _, _, xwfixed, _ := f.Glyph(fixed.P(0, 0), ch)
		xw := xwfixed.Ceil()
		if text_width+xw >= space {
			return text[:i]
		}
		text_width += xw
	}
	return text
}

func widgetText(o *command.Buffer, b rect.Rect, str string, t *textWidget, a label.Align, f font.Face) {
	b.H = max(b.H, 2*t.Padding.Y)
	lblrect := rect.Rect{X: 0, W: 0, Y: b.Y + t.Padding.Y, H: b.H - 2*t.Padding.Y}

	switch a[0] {
	case 'L':
		lblrect.X = b.X + t.Padding.X
		lblrect.W = max(0, b.W-2*t.Padding.X)
	case 'C':
		text_width := FontWidth(f, str)
		text_width += (2.0 * t.Padding.X)
		lblrect.W = max(1, 2*t.Padding.X+text_width)
		lblrect.X = (b.X + t.Padding.X + ((b.W-2*t.Padding.X)-lblrect.W)/2)
		lblrect.X = max(b.X+t.Padding.X, lblrect.X)
		lblrect.W = min(b.X+b.W, lblrect.X+lblrect.W)
		if lblrect.W >= lblrect.X {
			lblrect.W -= lblrect.X
		}
	case 'R':
		text_width := FontWidth(f, str)
		text_width += (2.0 * t.Padding.X)
		lblrect.X = max(b.X+t.Padding.X, (b.X+b.W)-(2*t.Padding.X+text_width))
		lblrect.W = text_width + 2*t.Padding.X
	default:
		panic("unsupported alignment")
	}

	if len(a) >= 2 {
		switch a[1] {
		case 'C':
			lblrect.Y = b.Y + b.H/2.0 - FontHeight(f)/2.0
		case 'B':
			lblrect.Y = b.Y + b.H - FontHeight(f)
		}
	}
	if lblrect.H < FontHeight(f)*2 {
		lblrect.H = FontHeight(f) * 2
	}

	o.DrawText(lblrect, str, f, t.Text)
}

func widgetTextWithBackground(o *command.Buffer, b rect.Rect, str string, t *textWidget, a label.Align, f font.Face) {
	b.H = max(b.H, 2*t.Padding.Y)
	lblrect := rect.Rect{X: 0, W: 0, Y: b.Y + t.Padding.Y, H: b.H - 2*t.Padding.Y}

	switch a[0] {
	case 'L':
		lblrect.X = b.X + t.Padding.X
		lblrect.W = max(0, b.W-2*t.Padding.X)
	case 'C':
		text_width := FontWidth(f, str)
		text_width += (2.0 * t.Padding.X)
		lblrect.W = max(1, 2*t.Padding.X+text_width)
		lblrect.X = (b.X + t.Padding.X + ((b.W-2*t.Padding.X)-lblrect.W)/2)
		lblrect.X = max(b.X+t.Padding.X, lblrect.X)
		lblrect.W = min(b.X+b.W, lblrect.X+lblrect.W)
		if lblrect.W >= lblrect.X {
			lblrect.W -= lblrect.X
		}
	case 'R':
		text_width := FontWidth(f, str)
		text_width += (2.0 * t.Padding.X)
		lblrect.X = max(b.X+t.Padding.X, (b.X+b.W)-(2*t.Padding.X+text_width))
		lblrect.W = text_width + 2*t.Padding.X
	default:
		panic("unsupported alignment")
	}

	if len(a) >= 2 {
		switch a[1] {
		case 'C':
			lblrect.Y = b.Y + b.H/2.0 - FontHeight(f)/2.0
		case 'B':
			lblrect.Y = b.Y + b.H - FontHeight(f)
		}
	}
	lblrect.H = FontHeight(f)

	o.FillRect(lblrect, 0, t.Background)
	o.DrawText(lblrect, str, f, t.Text)
}

func widgetTextWrap(o *command.Buffer, b rect.Rect, str []rune, t *textWidget, f font.Face) {
	var done int = 0
	var line rect.Rect
	var text textWidget

	text.Padding = image.Point{0, 0}
	text.Background = t.Background
	text.Text = t.Text

	b.W = max(b.W, 2*t.Padding.X)
	b.H = max(b.H, 2*t.Padding.Y)
	b.H = b.H - 2*t.Padding.Y

	line.X = b.X + t.Padding.X
	line.Y = b.Y + t.Padding.Y
	line.W = b.W - 2*t.Padding.X
	line.H = 2*t.Padding.Y + FontHeight(f)

	fitting := textClamp(f, str, line.W)
	for done < len(str) {
		if len(fitting) == 0 || line.Y+line.H >= (b.Y+b.H) {
			break
		}
		widgetText(o, line, string(fitting), &text, "LC", f)
		done += len(fitting)
		line.Y += FontHeight(f) + 2*t.Padding.Y
		fitting = textClamp(f, str[done:], line.W)
	}
}

type propertyStatus int

const (
	propertyDefault = propertyStatus(iota)
	propertyEdit
	propertyDrag
)

type TextEditor struct {
	win            *Window
	propertyStatus propertyStatus
	Cursor         int
	Buffer         []rune
	Filter         FilterFunc
	Flags          EditFlags
	CursorFollow   bool
	Redraw         bool
	Font           font.Face

	Maxlen int

	PasswordChar rune 

	Initialized            bool
	Active                 bool
	InsertMode             bool
	Scrollbar              image.Point
	SelectStart, SelectEnd int
	HasPreferredX          bool
	SingleLine             bool
	PreferredX             int
	Undo                   textUndoState

	drawchunks []drawchunk

	lastClickCoord  image.Point
	lastClickTime   time.Time
	clickCount      int
	trueSelectStart int

	needle []rune

	password []rune 

	WrapColumn int

	Styler mdstyle.Styler
	Theme  *themes.GuiTheme

	textArea          rect.Rect
	firstVisibleLine  int
	firstVisibleIndex int
	lastVisibleLine   int
	lastVisibleIndex  int
	charSize          int

	imageCache          map[string]*image.RGBA
	imagesInCurrentLine []*image.RGBA
	ShowImages          bool

	FileName     string
	FileBasename string
	Dirty        bool
}

func (te *TextEditor) SetFileName(fn string) {
	te.FileName = fn
	te.FileBasename = filepath.Base(fn)
}

type drawchunk struct {
	rect.Rect
	start, end int
}

func (ed *TextEditor) init(win *Window) {
	if ed.Filter == nil {
		ed.Filter = FilterDefault
	}
	if !ed.Initialized {
		if ed.Flags&EditMultiline != 0 {
			ed.clearState(TextEditMultiLine)
		} else {
			ed.clearState(TextEditSingleLine)
		}

	}
	if ed.win == nil || ed.win != win {
		if ed.win == nil {
			if ed.Buffer == nil {
				ed.Buffer = []rune{}
			}
			ed.Filter = nil
			ed.Cursor = 0
		}
		ed.Redraw = true
		ed.win = win
	}

	f := ed.Font
	if f == nil {
		ed.Font = win.ctx.Style.Font
		f = ed.Font
	}
	d := font.Drawer{Face: f}
	ed.charSize = d.MeasureString("M").Ceil()
}

func (ed *TextEditor) Construct(styler mdstyle.Styler, theme themes.GuiTheme) {
	ed.Styler = styler
	ed.Theme = &theme
	ed.lastVisibleIndex = 10240
}

type EditFlags int

const (
	EditDefault  EditFlags = 0
	EditReadOnly EditFlags = 1 << iota
	EditAutoSelect
	EditSigEnter
	EditNoCursor
	EditSelectable
	EditClipboard
	EditCtrlEnterNewline
	EditNoHorizontalScroll
	EditAlwaysInsertMode
	EditMultiline
	EditNeverInsertMode
	EditFocusFollowsMouse
	EditNoContextMenu
	EditIbeamCursor

	EditSimple = EditAlwaysInsertMode
	EditField  = EditSelectable | EditClipboard | EditSigEnter
	EditBox    = EditSelectable | EditMultiline | EditClipboard
)

type EditEvents int

const (
	EditActive EditEvents = 1 << iota
	EditInactive
	EditActivated
	EditDeactivated
	EditCommitted
)

type TextEditType int

const (
	TextEditSingleLine TextEditType = iota
	TextEditMultiLine
)

type textUndoRecord struct {
	Where        int
	InsertLength int
	DeleteLength int
	Text         []rune
}

const _TEXTEDIT_UNDOSTATECOUNT = 99

type textUndoState struct {
	UndoRec   [_TEXTEDIT_UNDOSTATECOUNT]textUndoRecord
	UndoPoint int16
	RedoPoint int16
}

func strInsertText(str []rune, pos int, runes []rune) []rune {
	if cap(str) < len(str)+len(runes) {
		newcap := (cap(str) + 1) * 2
		if newcap < len(str)+len(runes) {
			newcap = len(str) + len(runes)
		}
		newstr := make([]rune, len(str), newcap)
		copy(newstr, str)
		str = newstr
	}
	str = str[:len(str)+len(runes)]
	copy(str[pos+len(runes):], str[pos:])
	copy(str[pos:], runes)
	return str
}

func strDeleteText(s []rune, pos int, dlen int) []rune {
	copy(s[pos:], s[pos+dlen:])
	s = s[:len(s)-dlen]
	return s
}

func (s *TextEditor) hasSelection() bool {
	return s.SelectStart != s.SelectEnd
}

func (edit *TextEditor) locateCoord(p image.Point, font font.Face, row_height int) int {
	x, y := p.X, p.Y

	var drawchunk *drawchunk

	for i := range edit.drawchunks {
		min := edit.drawchunks[i].Min()
		max := edit.drawchunks[i].Max()
		if min.Y <= y && y <= max.Y {
			if min.X <= x && x <= max.X {
				drawchunk = &edit.drawchunks[i]

			}
		}
	}

	if drawchunk == nil {
		return len(edit.Buffer)
	}

	curx := drawchunk.X
	for i := drawchunk.start; i < drawchunk.end && i < len(edit.Buffer); i++ {
		curx += FontWidth(font, string(edit.Buffer[i:i+1]))
		if curx > x {
			return i
		}
	}

	if drawchunk.end >= len(edit.Buffer) {
		return len(edit.Buffer)
	}

	return drawchunk.end
}

func (edit *TextEditor) indexToCoord(index int, font font.Face, row_height int) image.Point {
	var drawchunk *drawchunk

	for i := range edit.drawchunks {
		if edit.drawchunks[i].start > index {
			if i == 0 {
				drawchunk = &edit.drawchunks[0]
			} else {
				drawchunk = &edit.drawchunks[i-1]
			}
			break
		}
	}

	if drawchunk == nil {
		if len(edit.drawchunks) == 0 {
			return image.Point{}
		}
		drawchunk = &edit.drawchunks[len(edit.drawchunks)-1]
	}

	x := drawchunk.X
	for i := drawchunk.start; i < drawchunk.end && i < len(edit.Buffer); i++ {
		if i >= index {
			break
		}
		x += FontWidth(font, string(edit.Buffer[i:i+1]))
	}
	if index >= len(edit.Buffer) && len(edit.Buffer) > 0 && edit.Buffer[len(edit.Buffer)-1] == '\n' {
		return image.Point{x, drawchunk.Y + drawchunk.H + drawchunk.H/2}
	}

	if index >= len(edit.Buffer) && len(edit.Buffer) > 0 && edit.Buffer[len(edit.Buffer)-1] == '\r' {
		return image.Point{x, drawchunk.Y + drawchunk.H + drawchunk.H/2}
	}
	return image.Point{x, drawchunk.Y + drawchunk.H/2}
}

func (state *TextEditor) doubleClick(coord image.Point) bool {
	abs := func(x int) int {
		if x < 0 {
			return -x
		}
		return x
	}
	r := time.Since(state.lastClickTime) < 200*time.Millisecond && abs(state.lastClickCoord.X-coord.X) < 5 && abs(state.lastClickCoord.Y-coord.Y) < 5
	state.lastClickCoord = coord
	state.lastClickTime = time.Now()
	return r

}

func (state *TextEditor) click(coord image.Point, font font.Face, row_height int) {

	state.Cursor = state.locateCoord(coord, font, row_height)

	state.SelectStart = state.Cursor
	state.trueSelectStart = state.Cursor
	state.SelectEnd = state.Cursor
	state.HasPreferredX = false

	switch state.clickCount {
	case 2:
		state.selectWord(state.SelectEnd)
	case 3:
		state.selectLine(state.SelectEnd)
	}
}

func (state *TextEditor) drag(coord image.Point, font font.Face, row_height int) {

	var p int = state.locateCoord(coord, font, row_height)
	if state.SelectStart == state.SelectEnd {
		state.SelectStart = state.Cursor
		state.trueSelectStart = p
	}
	state.SelectEnd = p
	state.Cursor = state.SelectEnd

	switch state.clickCount {
	case 2:
		state.selectWord(p)
	case 3:
		state.selectLine(p)
	}
}

func (state *TextEditor) selectWord(end int) {
	state.SelectStart = state.trueSelectStart
	state.SelectEnd = end
	state.sortselection()
	state.SelectStart = state.towd(state.SelectStart, -1, false)
	state.SelectEnd = state.towd(state.SelectEnd, +1, true)
}

func (state *TextEditor) selectLine(end int) {
	state.SelectStart = state.trueSelectStart
	state.SelectEnd = end
	state.sortselection()
	state.SelectStart = state.tonl(state.SelectStart-1, -1)
	state.SelectEnd = state.tonl(state.SelectEnd, +1)
}

func (state *TextEditor) clamp() {

	if state.hasSelection() {
		if state.SelectStart > len(state.Buffer) {
			state.SelectStart = len(state.Buffer)
		}
		if state.SelectEnd > len(state.Buffer) {
			state.SelectEnd = len(state.Buffer)
		}

		if state.SelectStart == state.SelectEnd {
			state.Cursor = state.SelectStart
		}
	}

	if state.Cursor > len(state.Buffer) {
		state.Cursor = len(state.Buffer)
	}
}

func (edit *TextEditor) Delete(where int, len int) {

	edit.makeundoDelete(where, len)

	edit.Buffer = strDeleteText(edit.Buffer, where, len)
	edit.HasPreferredX = false
}

func (edit *TextEditor) DeleteSelection() {

	edit.clamp()

	if edit.hasSelection() {
		if edit.SelectStart < edit.SelectEnd {
			edit.Delete(edit.SelectStart, edit.SelectEnd-edit.SelectStart)
			edit.Cursor = edit.SelectStart
			edit.SelectEnd = edit.Cursor
		} else {
			edit.Delete(edit.SelectEnd, edit.SelectStart-edit.SelectEnd)
			edit.Cursor = edit.SelectEnd
			edit.SelectStart = edit.Cursor
		}

		edit.HasPreferredX = false
	}
}

func (state *TextEditor) sortselection() {

	if state.SelectEnd < state.SelectStart {
		var temp int = state.SelectEnd
		state.SelectEnd = state.SelectStart
		state.SelectStart = temp
	}
}

func (state *TextEditor) moveToFirst() {

	if state.hasSelection() {
		state.sortselection()
		state.Cursor = state.SelectStart
		state.SelectEnd = state.SelectStart
		state.HasPreferredX = false
	}
}

func (state *TextEditor) moveToLast() {

	if state.hasSelection() {
		state.sortselection()
		state.clamp()
		state.Cursor = state.SelectEnd
		state.SelectStart = state.SelectEnd
		state.HasPreferredX = false
	}
}

func (state *TextEditor) tonl(start int, dir int) int {
	sz := len(state.Buffer)

	i := start
	if i < 0 {
		return 0
	}
	if i >= sz {
		i = sz - 1
	}
	for ; (i >= 0) && (i < sz); i += dir {
		c := state.Buffer[i]

		if c == '\n' {
			if dir >= 0 {
				return i
			} else {
				return i + 1
			}
		}
	}
	if dir < 0 {
		return 0
	} else {
		return sz
	}
}

func (state *TextEditor) towd(start int, dir int, dontForceAdvance bool) int {
	first := (dir < 0)
	notfirst := !first
	var i int
	for i = start; (i >= 0) && (i < len(state.Buffer)); i += dir {
		c := state.Buffer[i]
		if !(unicode.IsLetter(c) || unicode.IsDigit(c) || (c == '_')) {
			if !first && !dontForceAdvance {
				i++
			}
			break
		}
		first = notfirst
	}
	if i < 0 {
		i = 0
	}
	return i
}

func (state *TextEditor) tospc(start int, dir int) int {
	return state.tof(start, dir, unicode.IsSpace)
}

func (state *TextEditor) tof(start int, dir int, f func(rune) bool) int {
	first := (dir < 0)
	notfirst := !first
	var i int
	for i = start; (i >= 0) && (i < len(state.Buffer)); i += dir {
		c := state.Buffer[i]
		if f(c) {
			if !first {
				i++
			}
			break
		}
		first = notfirst
	}
	if i < 0 {
		i = 0
	}
	return i
}

func (state *TextEditor) tofp(start int, dir int) int {
	first := (dir < 0)
	notfirst := !first
	var i int
	for i = start; (i >= 0) && (i < len(state.Buffer)); i += dir {
		c := state.Buffer[i]
		if !(unicode.IsLetter(c) || unicode.IsDigit(c) || (c == '_') || (c == '-') || (c == '+') || (c == '/') || (c == '=') || (c == '~') || (c == '!') || (c == ':') || (c == ',') || (c == '.')) {
			if !first {
				i++
			}
			break
		}
		first = notfirst
	}
	if i < 0 {
		i = 0
	}
	return i

}

func (state *TextEditor) prepSelectionAtCursor() {

	if !state.hasSelection() {
		state.SelectEnd = state.Cursor
		state.SelectStart = state.SelectEnd
	} else {
		state.Cursor = state.SelectEnd
	}
}

func (edit *TextEditor) Cut() int {
	if edit.Flags&EditReadOnly != 0 {
		return 0
	}

	if edit.hasSelection() {
		edit.DeleteSelection() 
		edit.HasPreferredX = false
		edit.Dirty = true
		return 1
	}

	return 0
}

func (edit *TextEditor) Paste(ctext string) {
	if edit.Flags&EditReadOnly != 0 {
		return
	}

	edit.clamp()

	edit.DeleteSelection()

	text := []rune(ctext)

	edit.Buffer = strInsertText(edit.Buffer, edit.Cursor, text)

	edit.makeundoInsert(edit.Cursor, len(text))
	edit.Cursor += len(text)
	edit.HasPreferredX = false
	edit.Dirty = true
}

func (edit *TextEditor) ReflowNotGoodForBigFiles(onlystyle bool) {
	if edit.WrapColumn <= 0 {
		return
	}
	if edit.SingleLine {
		return
	}
	_tstart := time.Now()
	if !onlystyle {

		var ret string

		s := string(edit.Buffer)
		num_breaks_existing_before_cursor := strings.Count(s[:edit.Cursor], "\r")

		s = strings.ReplaceAll(s, "\r", "")
		for _, str := range strings.Split(s, "\n") {
			ret += edit.WrapLine(str, edit.WrapColumn) + "\n"
		}

		edit.Buffer = []rune(ret)

		num_breaks_new_before_cursor := strings.Count(ret[:edit.Cursor], "\r")
		if num_breaks_new_before_cursor > num_breaks_existing_before_cursor {
			edit.Cursor += num_breaks_new_before_cursor - num_breaks_existing_before_cursor
		}
	}

	fmt.Printf("%s - reflow\n", time.Since(_tstart))

	_tstart = time.Now()
	if edit.Styler != nil {
		styleDelta := 10240
		styleFrom := edit.firstVisibleIndex - styleDelta
		if styleFrom < 0 {
			styleFrom = 0
		}
		styleTo := edit.lastVisibleIndex + styleDelta
		if styleTo >= len(edit.Buffer) {
			styleTo = len(edit.Buffer) - 1
		}
		edit.Styler.Style(edit.Buffer, styleFrom, styleTo)
	}
	fmt.Printf("%s - styler\n\n\n", time.Since(_tstart))
}

func (edit *TextEditor) WrapLine(s string, limit int) string {
	if limit < 1 || utf8.RuneCountInString(s) < limit+1 {
		return s
	}

	breakpoints := " -"

	i := strings.LastIndexAny(s[:limit+1], breakpoints)

	if i < 0 {
		i = limit
	}

	return s[:i+1] + "\r" + edit.WrapLine(s[i+1:], limit)
}

func (edit *TextEditor) Reflow(onlystyle bool) {

	_tstart := time.Now()
	if edit.WrapColumn <= 0 {
		return
	}
	if edit.SingleLine {
		return
	}
	if !onlystyle {
		newBuffer := make([]rune, 2*len(edit.Buffer))

		dest_index := 0
		num_breaks_existing_before_cursor := 0
		num_breaks_new_before_cursor := 0
		pos_in_line := 0
		srcindex := 0
		for srcindex = 0; srcindex < len(edit.Buffer); {
			r := edit.Buffer[srcindex]
			if r == '\r' {
				if srcindex <= edit.Cursor {
					num_breaks_existing_before_cursor++
				}
				srcindex++
			}

			if r == '\n' {
				pos_in_line = 0
				newBuffer[dest_index] = r
				dest_index++
				srcindex++
			} else {
				current_word_length := edit.getWordLen(srcindex)
				if current_word_length+pos_in_line > edit.WrapColumn { 

					if pos_in_line != 0 {
						newBuffer[dest_index] = '\r'
						dest_index++

						if srcindex <= edit.Cursor {
							num_breaks_new_before_cursor++
						}
					}

					pos_in_line = 0
					if current_word_length >= edit.WrapColumn {

						src_advance := edit.copyWithoutSoftbreaks(srcindex, edit.WrapColumn, newBuffer, dest_index)
						dest_index += edit.WrapColumn
						srcindex += src_advance
						newBuffer[dest_index] = '\r'

						if srcindex <= edit.Cursor {
							num_breaks_new_before_cursor++
						}
						dest_index++
						continue
					}
				} else {

					src_advance := edit.copyWithoutSoftbreaks(srcindex, current_word_length, newBuffer, dest_index)
					dest_index += current_word_length
					srcindex += src_advance
					pos_in_line += current_word_length
				}
			}
		}

		if num_breaks_new_before_cursor > num_breaks_existing_before_cursor {
			edit.Cursor += num_breaks_new_before_cursor - num_breaks_existing_before_cursor
		}
		edit.Buffer = newBuffer[:dest_index]
		fmt.Printf("%s - reflow\n", time.Since(_tstart))
	}

	_tstart = time.Now()
	if edit.Styler != nil {
		styleDelta := 10240
		styleFrom := edit.firstVisibleIndex - styleDelta
		if styleFrom < 0 {
			styleFrom = 0
		}
		styleTo := edit.lastVisibleIndex + styleDelta
		if styleTo >= len(edit.Buffer) {
			styleTo = len(edit.Buffer) - 1
		}
		edit.Styler.Style(edit.Buffer, styleFrom, styleTo)
	}
	fmt.Printf("%s - styler\n\n\n", time.Since(_tstart))
}

func (edit *TextEditor) copyWithoutSoftbreaks(srcindex int, length int, dest []rune, dest_index int) int {
	i := 0
	L := len(edit.Buffer)
	num_copied := 0
	for i = 0; num_copied < length; {
		if srcindex+i >= L {
			break
		}
		if edit.Buffer[srcindex+i] != '\r' {
			dest[dest_index] = edit.Buffer[srcindex+i]
			dest_index++
			num_copied++
		}
		i++
	}
	return i
}

func (edit *TextEditor) getWordLen(pos int) int {
	wordlength := 0
	for srcindex := pos; srcindex < len(edit.Buffer); srcindex++ {
		r := edit.Buffer[srcindex]

		if r == '\r' {
			continue
		}

		if r == ' ' || r == '\n' {

			if wordlength == 0 {
				wordlength = 1
			}
			break
		}
		wordlength++
	}
	return wordlength
}
func (edit *TextEditor) ReflowStatic() {

	if edit.WrapColumn <= 0 {
		return
	}
	if !edit.SingleLine {

		pos_in_line := 0
		num_inserts := 0
		num_chars := 0
		num_existing_softbreaks := 0
		for srcindex, r := range edit.Buffer {
			if r == '\r' {
				num_existing_softbreaks++
				continue
			}
			num_chars++
			pos_in_line++
			if r == '\n' {
				pos_in_line = 1
			}
			if pos_in_line == edit.WrapColumn+1 {
				if srcindex != len(edit.Buffer)-1 {
					num_inserts++
					pos_in_line = 1
				}
			}
		}

		str := edit.Buffer
		newstr := make([]rune, num_chars+num_inserts)
		dest_index := 0
		pos_in_line = 0
		for srcindex, r := range str {
			if r == '\r' {
				continue
			}
			pos_in_line++
			if r == '\n' {
				pos_in_line = 1
			}
			if pos_in_line == edit.WrapColumn+1 {
				if srcindex != len(str)-1 {
					newstr[dest_index] = '\r'
					dest_index++
					pos_in_line = 1
				}
			}
			newstr[dest_index] = r
			dest_index++
		}
		edit.Buffer = newstr
		if num_inserts > num_existing_softbreaks {
			edit.Cursor += num_inserts - num_existing_softbreaks
		}

		log.Printf("Cursor: %v, len buffer: %v", edit.Cursor, len(edit.Buffer))
		if edit.Cursor > len(edit.Buffer)+1 {

			edit.Cursor = len(edit.Buffer) + 1
		}
	}
}

func (edit *TextEditor) Text(text []rune) {
	if edit.Flags&EditReadOnly != 0 {
		return
	}

	for i := range text {

		if text[i] == '\n' && edit.SingleLine {
			break
		}

		if text[i] == '\t' && edit.SingleLine {
			break
		}

		if edit.Filter != nil && !edit.Filter(text[i]) {
			continue
		}

		if edit.InsertMode && !edit.hasSelection() && edit.Cursor < len(edit.Buffer) {
			edit.makeundoReplace(edit.Cursor, 1, 1)
			edit.Buffer = strDeleteText(edit.Buffer, edit.Cursor, 1)
			edit.Buffer = strInsertText(edit.Buffer, edit.Cursor, text[i:i+1])
			edit.Cursor++
			edit.HasPreferredX = false
		} else {
			edit.DeleteSelection() 
			edit.Buffer = strInsertText(edit.Buffer, edit.Cursor, text[i:i+1])
			edit.makeundoInsert(edit.Cursor, 1)
			edit.Cursor++
			edit.HasPreferredX = false
		}
	}
	edit.Dirty = true
}

func (state *TextEditor) key(e key.Event, font font.Face, row_height int) {
	readOnly := state.Flags&EditReadOnly != 0
retry:
	switch e.Code {
	case key.CodeZ:
		if readOnly {
			return
		}
		if e.Modifiers&key.ModControl != 0 {
			if e.Modifiers&key.ModShift != 0 {
				state.DoRedo()
				state.HasPreferredX = false

			} else {
				state.DoUndo()
				state.HasPreferredX = false
			}
		}

	case key.CodeK:
		if readOnly {
			return
		}
		if e.Modifiers&key.ModControl != 0 {
			state.trueSelectStart = state.Cursor
			state.selectLine(state.Cursor)
			state.DeleteSelection()
		}

	case key.CodeInsert:
		state.InsertMode = !state.InsertMode

	case key.CodeLeftArrow:
		if e.Modifiers&key.ModControl != 0 {
			if e.Modifiers&key.ModShift != 0 {
				if !state.hasSelection() {
					state.prepSelectionAtCursor()
				}
				state.Cursor = state.towd(state.Cursor-1, -1, false)
				state.SelectEnd = state.Cursor
				state.clamp()
			} else {
				if state.hasSelection() {
					state.moveToFirst()
				} else {
					state.Cursor = state.towd(state.Cursor-1, -1, false)
					state.clamp()
				}
			}
		} else {
			if e.Modifiers&key.ModShift != 0 {
				state.clamp()
				state.prepSelectionAtCursor()

				if state.SelectEnd > 0 {
					state.SelectEnd--
				}
				state.Cursor = state.SelectEnd
				state.HasPreferredX = false
			} else {

				if state.hasSelection() {
					state.moveToFirst()
				} else if state.Cursor > 0 {
					state.Cursor--
				}
				state.HasPreferredX = false
			}
		}

	case key.CodeRightArrow:
		if e.Modifiers&key.ModControl != 0 {
			if e.Modifiers&key.ModShift != 0 {
				if !state.hasSelection() {
					state.prepSelectionAtCursor()
				}
				state.Cursor = state.towd(state.Cursor, +1, false)
				state.SelectEnd = state.Cursor
				state.clamp()
			} else {
				if state.hasSelection() {
					state.moveToLast()
				} else {
					state.Cursor = state.towd(state.Cursor, +1, false)
					state.clamp()
				}
			}
		} else {
			if e.Modifiers&key.ModShift != 0 {
				state.prepSelectionAtCursor()

				state.SelectEnd++

				state.clamp()
				state.Cursor = state.SelectEnd
				state.HasPreferredX = false
			} else {

				if state.hasSelection() {
					state.moveToLast()
				} else {
					state.Cursor++
				}
				state.clamp()
				state.HasPreferredX = false
			}
		}
	case key.CodeDownArrow:
		if state.SingleLine {
			e.Code = key.CodeRightArrow
			goto retry
		}

		if e.Modifiers&key.ModShift != 0 {
			if !state.hasSelection() {
				state.prepSelectionAtCursor()
			}
		} else if state.hasSelection() {
			state.moveToLast()
		}
		state.clamp()

		p := state.indexToCoord(state.Cursor, font, row_height)
		p.Y += row_height
		if state.HasPreferredX {
			p.X = state.PreferredX
		} else {
			state.HasPreferredX = true
			state.PreferredX = p.X
		}
		state.Cursor = state.locateCoord(p, font, row_height)

		if e.Modifiers&key.ModShift != 0 {
			state.SelectEnd = state.Cursor
		}
		state.clamp()

	case key.CodeUpArrow:
		if state.SingleLine {
			e.Code = key.CodeRightArrow
			goto retry
		}

		if e.Modifiers&key.ModShift != 0 {
			if !state.hasSelection() {
				state.prepSelectionAtCursor()
			}
		} else if state.hasSelection() {
			state.moveToFirst()
		}

		state.clamp()

		p := state.indexToCoord(state.Cursor, font, row_height)
		p.Y -= row_height
		if state.HasPreferredX {
			p.X = state.PreferredX
		} else {
			state.HasPreferredX = true
			state.PreferredX = p.X
		}
		state.Cursor = state.locateCoord(p, font, row_height)
		if state.Cursor >= len(state.Buffer) {
			state.Cursor = 0
		}

		if e.Modifiers&key.ModShift != 0 {
			state.SelectEnd = state.Cursor
		}
		state.clamp()

	case key.CodePageUp:
		if state.SingleLine {
			e.Code = key.CodeRightArrow
			goto retry
		}

		if e.Modifiers&key.ModShift != 0 {
			if !state.hasSelection() {
				state.prepSelectionAtCursor()
			}
		} else if state.hasSelection() {
			state.moveToFirst()
		}

		state.clamp()

		linesUp := state.lastVisibleLine - state.firstVisibleLine
		p := state.indexToCoord(state.Cursor, font, row_height)
		p.Y -= row_height * linesUp
		if state.HasPreferredX {
			p.X = state.PreferredX
		} else {
			state.HasPreferredX = true
			state.PreferredX = p.X
		}
		state.Cursor = state.locateCoord(p, font, row_height)
		if state.Cursor >= len(state.Buffer) {
			state.Cursor = 0
		}
		if e.Modifiers&key.ModShift != 0 {
			state.SelectEnd = state.Cursor
		}
		state.clamp()

	case key.CodePageDown:
		if state.SingleLine {
			e.Code = key.CodeRightArrow
			goto retry
		}

		if e.Modifiers&key.ModShift != 0 {
			if !state.hasSelection() {
				state.prepSelectionAtCursor()
			}
			state.prepSelectionAtCursor()
		} else if state.hasSelection() {
			state.moveToFirst()
		}

		state.clamp()

		linesDown := state.lastVisibleLine - state.firstVisibleLine
		p := state.indexToCoord(state.Cursor, font, row_height)
		p.Y += row_height * linesDown
		if state.HasPreferredX {
			p.X = state.PreferredX
		} else {
			state.HasPreferredX = true
			state.PreferredX = p.X
		}
		state.Cursor = state.locateCoord(p, font, row_height)
		if state.Cursor >= len(state.Buffer) {
			state.Cursor = len(state.Buffer) - 1
		}
		if e.Modifiers&key.ModShift != 0 {
			state.SelectEnd = state.Cursor
		}
		state.clamp()

	case key.CodeDeleteForward:
		if readOnly {
			return
		}
		if state.hasSelection() {
			state.DeleteSelection()
		} else {
			if state.Cursor < len(state.Buffer) {
				state.Delete(state.Cursor, 1)
			}
		}

		state.HasPreferredX = false

	case key.CodeDeleteBackspace:
		if readOnly {
			return
		}
		switch {
		case state.hasSelection():
			state.DeleteSelection()
		case e.Modifiers&key.ModControl != 0:
			state.SelectEnd = state.Cursor
			state.SelectStart = state.towd(state.Cursor-1, -1, false)
			state.DeleteSelection()
		default:
			state.clamp()
			if state.Cursor > 0 {

				if state.Buffer[state.Cursor-1] == '\r' {
					state.Delete(state.Cursor-2, 2)

					state.Cursor -= 3
				} else {
					state.Delete(state.Cursor-1, 1)

					state.Cursor--
				}
			}

		}
		state.HasPreferredX = false

	case key.CodeHome:
		if e.Modifiers&key.ModControl != 0 {
			if e.Modifiers&key.ModShift != 0 {
				state.prepSelectionAtCursor()
				state.SelectEnd = 0
				state.Cursor = state.SelectEnd
				state.HasPreferredX = false
			} else {
				state.SelectEnd = 0
				state.SelectStart = state.SelectEnd
				state.Cursor = state.SelectStart
				state.HasPreferredX = false
			}
		} else {
			state.clamp()
			start := state.tonl(state.Cursor-1, -1)
			if e.Modifiers&key.ModShift != 0 {
				state.clamp()
				state.prepSelectionAtCursor()
				state.SelectEnd = start
				state.Cursor = state.SelectEnd
				state.HasPreferredX = false
			} else {
				state.clamp()
				state.moveToFirst()
				state.Cursor = start
				state.HasPreferredX = false
			}
		}

	case key.CodeA:
		if e.Modifiers&key.ModControl != 0 {

			if true {
				state.SelectAll()
			} else {
				state.clamp()
				state.moveToFirst()
				state.Cursor = state.tonl(state.Cursor-1, -1)
				state.HasPreferredX = false
			}
		}

	case key.CodeEnd:
		if e.Modifiers&key.ModControl != 0 {
			if e.Modifiers&key.ModShift != 0 {
				state.prepSelectionAtCursor()
				state.SelectEnd = len(state.Buffer)
				state.Cursor = state.SelectEnd
				state.HasPreferredX = false
			} else {
				state.Cursor = len(state.Buffer)
				state.SelectEnd = 0
				state.SelectStart = state.SelectEnd
				state.HasPreferredX = false
			}
		} else {
			state.clamp()
			end := state.tonl(state.Cursor, +1)
			if e.Modifiers&key.ModShift != 0 {
				state.clamp()
				state.prepSelectionAtCursor()
				state.HasPreferredX = false
				state.Cursor = end
				state.SelectEnd = state.Cursor
			} else {
				state.clamp()
				state.moveToFirst()
				state.HasPreferredX = false
				state.Cursor = end
			}
		}

	case key.CodeE:
		if e.Modifiers&key.ModControl != 0 {
			end := state.tonl(state.Cursor, +1)
			state.clamp()
			state.moveToFirst()
			state.HasPreferredX = false
			state.Cursor = end
		}
	}

}

func texteditFlushRedo(state *textUndoState) {
	state.RedoPoint = int16(_TEXTEDIT_UNDOSTATECOUNT)
}

func texteditDiscardUndo(state *textUndoState) {

	if state.UndoPoint > 0 {
		state.UndoPoint--
		copy(state.UndoRec[:], state.UndoRec[1:])
	}
}

func texteditCreateUndoRecord(state *textUndoState, numchars int) *textUndoRecord {

	texteditFlushRedo(state)

	if int(state.UndoPoint) == _TEXTEDIT_UNDOSTATECOUNT {
		texteditDiscardUndo(state)
	}

	r := &state.UndoRec[state.UndoPoint]
	state.UndoPoint++
	return r
}

func texteditCreateundo(state *textUndoState, pos int, insert_len int, delete_len int) *textUndoRecord {
	r := texteditCreateUndoRecord(state, insert_len)

	r.Where = pos
	r.InsertLength = insert_len
	r.DeleteLength = delete_len
	r.Text = nil

	return r
}

func (edit *TextEditor) DoUndo() {
	var s *textUndoState = &edit.Undo
	var u textUndoRecord
	var r *textUndoRecord
	if s.UndoPoint == 0 {
		return
	}

	u = s.UndoRec[s.UndoPoint-1]

	r = &s.UndoRec[s.RedoPoint-1]
	r.Text = nil

	r.InsertLength = u.DeleteLength
	r.DeleteLength = u.InsertLength
	r.Where = u.Where

	if u.DeleteLength != 0 {
		r.Text = make([]rune, u.DeleteLength)
		copy(r.Text, edit.Buffer[u.Where:u.Where+u.DeleteLength])
		edit.Buffer = strDeleteText(edit.Buffer, u.Where, u.DeleteLength)
	}

	if u.InsertLength != 0 {

		edit.Buffer = strInsertText(edit.Buffer, u.Where, u.Text)
	}

	edit.Cursor = u.Where + u.InsertLength

	s.UndoPoint--
	s.RedoPoint--
}

func (edit *TextEditor) DoRedo() {
	var s *textUndoState = &edit.Undo
	var u *textUndoRecord
	var r textUndoRecord
	if int(s.RedoPoint) == _TEXTEDIT_UNDOSTATECOUNT {
		return
	}

	u = &s.UndoRec[s.UndoPoint]

	r = s.UndoRec[s.RedoPoint]

	u.DeleteLength = r.InsertLength

	u.InsertLength = r.DeleteLength
	u.Where = r.Where
	u.Text = nil

	if r.DeleteLength != 0 {
		u.Text = make([]rune, r.DeleteLength)
		copy(u.Text, edit.Buffer[r.Where:r.Where+r.DeleteLength])
		edit.Buffer = strDeleteText(edit.Buffer, r.Where, r.DeleteLength)
	}

	if r.InsertLength != 0 {

		edit.Buffer = strInsertText(edit.Buffer, r.Where, r.Text)
	}

	edit.Cursor = r.Where + r.InsertLength

	s.UndoPoint++
	s.RedoPoint++
}

func (state *TextEditor) makeundoInsert(where int, length int) {
	texteditCreateundo(&state.Undo, where, 0, length)
}

func (state *TextEditor) makeundoDelete(where int, length int) {
	u := texteditCreateundo(&state.Undo, where, length, 0)
	u.Text = make([]rune, length)
	copy(u.Text, state.Buffer[where:where+length])
}

func (state *TextEditor) makeundoReplace(where int, old_length int, new_length int) {
	u := texteditCreateundo(&state.Undo, where, old_length, new_length)
	u.Text = make([]rune, old_length)
	copy(u.Text, state.Buffer[where:where+old_length])
}

func (state *TextEditor) clearState(type_ TextEditType) {

	state.Undo.UndoPoint = 0

	state.Undo.RedoPoint = int16(_TEXTEDIT_UNDOSTATECOUNT)
	state.HasPreferredX = false
	state.PreferredX = 0

	state.Initialized = true
	state.SingleLine = type_ == TextEditSingleLine
	state.InsertMode = false
}

func (edit *TextEditor) SelectAll() {
	edit.SelectStart = 0
	edit.SelectEnd = len(edit.Buffer)
}

func (edit *TextEditor) editDrawText(out *command.Buffer, style *nstyle.Edit, pos image.Point, x_margin int, text []rune, textOffset int, row_height int, f font.Face, background color.RGBA, foreground color.RGBA, is_selected bool, md_style int) (posOut image.Point) {
	if len(text) == 0 {
		return pos
	}
	var line_offset int = 0
	var line_count int = 0
	var txt textWidget
	txt.Background = background
	txt.Text = foreground

	pos_x, pos_y := pos.X, pos.Y
	start := 0

	tabsz := edit.charSize * tabSizeInSpaces
	pwsz := edit.charSize

	measureText := func(start, end int) int {
		if edit.PasswordChar != 0 {
			return pwsz * (end - start)
		}
		return edit.charSize * (end - start)
	}

	getText := func(start, end int) string {
		if edit.PasswordChar != 0 {
			n := end - start
			if n >= len(edit.password) {
				edit.password = make([]rune, n)
				for i := range edit.password {
					edit.password[i] = edit.PasswordChar
				}
			}
			return string(edit.password[:n])
		}
		return string(text[start:end])
	}

	flushLine := func(index int) rect.Rect {

		var lblrect rect.Rect
		lblrect.Y = pos_y + line_offset
		lblrect.H = row_height
		lblrect.W = nk_null_rect.W
		lblrect.X = pos_x

		if is_selected { 
			if index == len(text) || (index == start && start == 0) {
				lblrect.W = measureText(start, index)
			}
			out.FillRect(lblrect, 0, background)
		}

		if md_style == mdstyle.MdStyleZkLinkText || md_style == mdstyle.MdStyleSearchSpec {
			sp := image.Point{pos_x, pos_y + row_height - 1}
			ep := image.Point{pos_x + edit.charSize*(index-start), pos_y + row_height - 1}
			out.StrokeLine(sp, ep, 1, edit.Theme.MarkDownTheme[md_style].Color)
		}

		edit.drawchunks = append(edit.drawchunks, drawchunk{lblrect, start + textOffset, index + textOffset})
		widgetText(out, lblrect, getText(start, index), &txt, "LC", f)

		pos_x = x_margin

		return lblrect
	}

	flushTab := func(index int) rect.Rect {
		var lblrect rect.Rect
		lblrect.Y = pos_y + line_offset
		lblrect.H = row_height
		lblrect.W = measureText(start, index)
		lblrect.X = pos_x

		lblrect.W = int(math.Floor(float64(lblrect.X+lblrect.W-x_margin)/float64(tabsz))+1)*tabsz + x_margin - lblrect.X

		if is_selected {
			out.FillRect(lblrect, 0, background)
		}
		edit.drawchunks = append(edit.drawchunks, drawchunk{lblrect, start + textOffset, index + textOffset})
		widgetText(out, lblrect, getText(start, index), &txt, "LC", f)

		pos_x += lblrect.W

		return lblrect
	}

	if start+textOffset == 0 {
		realLineLen := 0
		var llr rune
		for realLineLen, llr = range edit.Buffer[start+textOffset:] {
			if llr == '\r' || llr == '\n' {
				break
			}
		}

		if edit.Cursor >= start+textOffset && edit.Cursor <= start+textOffset+realLineLen {
			var lblrect rect.Rect
			lblrect.Y = pos_y + line_offset
			lblrect.H = row_height
			lblrect.W = nk_null_rect.W
			lblrect.X = x_margin
			out.FillRect(lblrect, 0, edit.Theme.LineHighlight)
		}

	}
	for index, glyph := range text {
		switch glyph {
		case '\t':
			if true || index+textOffset >= edit.firstVisibleIndex && index+textOffset <= edit.lastVisibleIndex {
				flushTab(index)
			}
			start = index + 1
		case '\n', '\r':
			if true || index+textOffset >= edit.firstVisibleIndex && index+textOffset <= edit.lastVisibleIndex {
				flushLine(index)
			}
			line_count++
			start = index + 1
			line_offset += row_height

			realLineLen := 0
			var llr rune
			for realLineLen, llr = range edit.Buffer[start+textOffset:] {
				if llr == '\r' || llr == '\n' {
					break
				}
			}

			if edit.ShowImages {
				var imgbox rect.Rect
				var imgbounds image.Rectangle
				for _, img := range edit.imagesInCurrentLine {
					imgbounds = img.Bounds()
					imgbox.Y = pos_y + line_offset
					imgbox.H = imgbounds.Max.Y - imgbounds.Min.Y
					imgbox.W = imgbounds.Max.X - imgbounds.Min.X
					imgbox.X = x_margin

					out.DrawImage(imgbox, img)
					pos_y += line_offset + imgbox.H
				}
				edit.imagesInCurrentLine = []*image.RGBA{}
			}

			if edit.Cursor >= start+textOffset && edit.Cursor <= start+textOffset+realLineLen {
				var lblrect rect.Rect
				lblrect.Y = pos_y + line_offset
				lblrect.H = row_height
				lblrect.W = nk_null_rect.W
				lblrect.X = x_margin
				out.FillRect(lblrect, 0, edit.Theme.LineHighlight)
			}
		}
	}

	if start >= len(text) {
		return image.Point{pos_x, pos_y + line_offset}
	}

	lblrect := flushLine(len(text))
	lblrect.W = measureText(start, len(text))

	if md_style == mdstyle.MdStyleImageUrl {
		strimg := string(text)
		img, ok := edit.imageCache[strimg]
		if ok {
			edit.imagesInCurrentLine = append(edit.imagesInCurrentLine, img)
		}
	}

	return image.Point{lblrect.X + lblrect.W, lblrect.Y}
}

func (ed *TextEditor) doEdit(bounds rect.Rect, style *nstyle.Edit, inp *Input, cut, copy, paste bool) (ret EditEvents) {
	font := ed.Font
	state := ed.win.widgets.PrevState(bounds)

	ed.clamp()

	var area rect.Rect
	area.X = bounds.X + style.Padding.X + style.Border
	area.Y = bounds.Y + style.Padding.Y + style.Border
	area.W = bounds.W - (2.0*style.Padding.X + 2*style.Border)
	area.H = bounds.H - (2.0*style.Padding.Y + 2*style.Border)
	if ed.Flags&EditMultiline != 0 {
		area.H = area.H - style.ScrollbarSize.Y
	}
	var row_height int
	if ed.Flags&EditMultiline != 0 {
		row_height = FontHeight(font) + ed.Theme.LinePadding
	} else {
		row_height = area.H
	}

	prev_state := ed.Active

	if ed.win.ctx.activateEditor != nil {
		if ed.win.ctx.activateEditor == ed {
			ed.Active = true
			if ed.win.flags&windowDocked != 0 {
				ed.win.ctx.dockedWindowFocus = ed.win.idx
			}
		} else {
			ed.Active = false
		}
	}

	is_hovered := inp.Mouse.HoveringRect(bounds)

	if ed.Flags&EditFocusFollowsMouse != 0 {
		if inp != nil {
			ed.Active = is_hovered
		}
	} else {
		if inp != nil && inp.Mouse.Buttons[mouse.ButtonLeft].Clicked && inp.Mouse.Buttons[mouse.ButtonLeft].Down {
			ed.Active = inp.Mouse.HoveringRect(bounds)
		}
	}

	var select_all bool
	if !prev_state && ed.Active {
		type_ := TextEditSingleLine
		if ed.Flags&EditMultiline != 0 {
			type_ = TextEditMultiLine
		}
		ed.clearState(type_)
		if ed.Flags&EditAlwaysInsertMode != 0 {
			ed.InsertMode = true
		}
		if ed.Flags&EditAutoSelect != 0 {
			select_all = true
		}
	} else if !ed.Active {
		ed.InsertMode = false
	}

	if ed.Flags&EditNeverInsertMode != 0 {
		ed.InsertMode = false
	}

	if ed.Active {
		ret = EditActive
	} else {
		ret = EditInactive
	}
	if prev_state != ed.Active {
		if ed.Active {
			ret |= EditActivated
		} else {
			ret |= EditDeactivated
		}
	}

	cursor_follow := ed.CursorFollow
	ed.CursorFollow = false
	do_reflow := false
	onlystyle := true
	if ed.Active && inp != nil {
		inpos := inp.Mouse.Pos
		indelta := inp.Mouse.Delta
		if indelta.X > 0 || indelta.Y > 0 {
			do_reflow = true
		}

		coord := image.Point{(inpos.X - area.X), (inpos.Y - area.Y)}
		areaWithoutScrollbar := area
		areaWithoutScrollbar.W -= style.ScrollbarSize.X
		is_hovered := inp.Mouse.HoveringRect(areaWithoutScrollbar)

		if select_all {
			ed.SelectAll()
			do_reflow = true
		} else if is_hovered && inp.Mouse.Buttons[mouse.ButtonLeft].Down && inp.Mouse.Buttons[mouse.ButtonLeft].Clicked {
			if ed.doubleClick(coord) {
				ed.clickCount++
				if ed.clickCount > 3 {
					ed.clickCount = 3
				}
				do_reflow = true
			} else {
				do_reflow = true
				ed.clickCount = 1
			}
			ed.click(coord, font, row_height)
		} else if is_hovered && inp.Mouse.Buttons[mouse.ButtonLeft].Down && (indelta.X != 0.0 || indelta.Y != 0.0) {
			do_reflow = true
			ed.drag(coord, font, row_height)
			cursor_follow = true
		}

		if inp.Keyboard.Text != "" {

			ed.Text([]rune(inp.Keyboard.Text))
			cursor_follow = true
			do_reflow = true
			onlystyle = false
		}

		clipboardModifier := key.ModControl
		if runtime.GOOS == "darwin" {
			clipboardModifier = key.ModMeta
		}

		for _, e := range inp.Keyboard.Keys {
			switch e.Code {
			case key.CodeReturnEnter:
				if ed.Flags&EditCtrlEnterNewline != 0 && e.Modifiers&key.ModShift != 0 {
					ed.Text([]rune{'\n'})
					cursor_follow = true
					onlystyle = false
				} else if ed.Flags&EditSigEnter != 0 {
					ret = EditInactive
					ret |= EditDeactivated
					if ed.Flags&EditReadOnly == 0 {
						ret |= EditCommitted
					}
					ed.Active = false
				}

			case key.CodeX:
				if e.Modifiers&clipboardModifier != 0 {
					cut = true
				}

			case key.CodeC:
				if e.Modifiers&clipboardModifier != 0 {
					copy = true
				}

			case key.CodeV:
				if e.Modifiers&clipboardModifier != 0 {
					paste = true
				}

			case key.CodeF:
				if e.Modifiers&clipboardModifier != 0 {
					ed.popupFind()
				}

			case key.CodeG:
				if e.Modifiers&clipboardModifier != 0 {
					ed.lookForward(true)
					cursor_follow = true
				}

			default:
				ed.key(e, font, row_height)
				cursor_follow = true
				onlystyle = false
			}
			do_reflow = true

		}

		if (copy || cut) && (ed.Flags&EditClipboard != 0) {
			var begin, end int
			if ed.SelectStart > ed.SelectEnd {
				begin = ed.SelectEnd
				end = ed.SelectStart
			} else {
				begin = ed.SelectStart
				end = ed.SelectEnd
			}
			clipboard.Set(string(ed.Buffer[begin:end]))
			if cut {
				onlystyle = false
				ed.Cut()
				cursor_follow = true
				do_reflow = true
			}
		}

		if paste && (ed.Flags&EditClipboard != 0) {
			ed.Paste(clipboard.Get())
			cursor_follow = true
			do_reflow = true
			onlystyle = false
		}

		if do_reflow {
			ed.Reflow(onlystyle)
		}
	}

	if ed.Active {
		state = nstyle.WidgetStateActive
	} else {
		state = nstyle.WidgetStateInactive
	}
	if is_hovered {
		state |= nstyle.WidgetStateHovered
	}

	var d drawableTextEditor

	var selection_begin, selection_end int
	if ed.SelectStart < ed.SelectEnd {
		selection_begin = ed.SelectStart
		selection_end = ed.SelectEnd
	} else {
		selection_begin = ed.SelectEnd
		selection_end = ed.SelectStart
	}

	d.SelectionBegin, d.SelectionEnd = selection_begin, selection_end

	d.Edit = ed
	d.State = state
	d.Style = style
	d.Scaling = ed.win.ctx.Style.Scaling
	d.Bounds = bounds
	d.Area = area
	ed.textArea = area
	d.RowHeight = row_height
	d.hasInput = inp.Mouse.valid
	ed.win.widgets.Add(state, bounds)

	d.Draw(&ed.win.ctx.Style, &ed.win.cmds)

	if cursor_follow {
		cursor_pos := d.CursorPos

		if ed.Flags&EditNoHorizontalScroll == 0 {

			scroll_increment := area.W / 2
			if (cursor_pos.X < ed.Scrollbar.X) || ((ed.Scrollbar.X+area.W)-cursor_pos.X < FontWidth(font, "i")) {
				ed.Scrollbar.X = max(0, cursor_pos.X-scroll_increment)
			}
		} else {
			ed.Scrollbar.X = 0
		}

		if ed.Flags&EditMultiline != 0 {

			if cursor_pos.Y < ed.Scrollbar.Y {
				ed.Scrollbar.Y = max(0, cursor_pos.Y-row_height)
			}
			for (ed.Scrollbar.Y+area.H)-cursor_pos.Y < row_height {
				ed.Scrollbar.Y = ed.Scrollbar.Y + row_height
			}
		} else {
			ed.Scrollbar.Y = 0
		}
	}

	if !ed.SingleLine {

		var scroll rect.Rect
		scroll.X = (area.X + area.W) - style.ScrollbarSize.X
		scroll.Y = area.Y
		scroll.W = style.ScrollbarSize.X
		scroll.H = area.H

		scroll_offset := float64(ed.Scrollbar.Y)
		scroll_step := float64(scroll.H) * 0.1
		if runtime.GOOS == "darwin" {
			scroll_step = float64(scroll.H) * 0.005 
		}
		scroll_inc := float64(scroll.H) * 0.01
		scroll_target := float64(d.TextSize.Y + row_height)
		ed.Scrollbar.Y = int(doScrollbarv(ed.win, scroll, bounds, scroll_offset, scroll_target, scroll_step, scroll_inc, &style.Scrollbar, inp, font))
	}

	return ret
}

type drawableTextEditor struct {
	Edit      *TextEditor
	State     nstyle.WidgetStates
	Style     *nstyle.Edit
	Scaling   float64
	Bounds    rect.Rect
	Area      rect.Rect
	RowHeight int
	hasInput  bool

	SelectionBegin, SelectionEnd int

	TextSize  image.Point
	CursorPos image.Point
}

func (d *drawableTextEditor) Draw(z *nstyle.Style, out *command.Buffer) {
	edit := d.Edit
	state := d.State
	style := d.Style
	bounds := d.Bounds
	font := edit.Font
	area := d.Area
	row_height := d.RowHeight

	selection_begin := d.SelectionBegin
	selection_end := d.SelectionEnd
	if edit.drawchunks != nil {
		edit.drawchunks = edit.drawchunks[:0]
	}

	var old_clip rect.Rect = out.Clip
	{
		var background *nstyle.Item
		if state&nstyle.WidgetStateActive != 0 {
			background = &style.Active
		} else if state&nstyle.WidgetStateHovered != 0 {
			background = &style.Hover
		} else {
			background = &style.Normal
		}

		if background.Type == nstyle.ItemColor {
			out.FillRect(bounds, style.Rounding, style.BorderColor)
			if edit.Theme != nil {
				out.FillRect(shrinkRect(bounds, style.Border), style.Rounding, edit.Theme.BgColor )
			} else {
				out.FillRect(shrinkRect(bounds, style.Border), style.Rounding, background.Data.Color)
			}
		} else {
			out.DrawImage(bounds, background.Data.Image)
		}
	}

	area.W -= FontWidth(font, "i")
	clip := unify(old_clip, area)
	out.PushScissor(clip)

	var background_color color.RGBA
	var text_color color.RGBA
	var sel_background_color color.RGBA
	var sel_text_color color.RGBA
	var cursor_color color.RGBA
	var cursor_text_color color.RGBA
	var background *nstyle.Item

	if state&nstyle.WidgetStateActive != 0 {
		background = &style.Active
		text_color = style.TextActive
		sel_text_color = style.SelectedTextHover
		sel_background_color = style.SelectedHover
		cursor_color = style.CursorHover
		cursor_text_color = style.CursorTextHover
	} else if state&nstyle.WidgetStateHovered != 0 {
		background = &style.Hover
		text_color = style.TextHover
		sel_text_color = style.SelectedTextHover
		sel_background_color = style.SelectedHover
		cursor_text_color = style.CursorTextHover
		cursor_color = style.CursorHover
	} else {
		background = &style.Normal
		text_color = style.TextNormal
		sel_text_color = style.SelectedTextNormal
		sel_background_color = style.SelectedNormal
		cursor_color = style.CursorNormal
		cursor_text_color = style.CursorTextNormal
	}

	if background.Type == nstyle.ItemImage {
		background_color = color.RGBA{0, 0, 0, 0}
	} else {
		background_color = background.Data.Color
	}

	if edit.Theme != nil {
		cursor_color = edit.Theme.CursorColor
		background_color = edit.Theme.BgColor
		sel_background_color = edit.Theme.SelectionColor
	}

	startPos := image.Point{area.X - edit.Scrollbar.X, area.Y - edit.Scrollbar.Y}
	pos := startPos
	x_margin := pos.X

	edit.firstVisibleLine = edit.Scrollbar.Y / row_height
	edit.lastVisibleLine = edit.firstVisibleLine + edit.textArea.H/row_height

	line_counter := 0
	edit.firstVisibleIndex = 0
	edit.lastVisibleIndex = len(edit.Buffer)
	for index, r := range edit.Buffer {
		switch r {
		case '\n', '\r':
			line_counter++
			if line_counter == edit.firstVisibleLine {
				edit.firstVisibleIndex = index + 1
			}
			if line_counter == edit.lastVisibleLine+1 {
				edit.lastVisibleIndex = index - 1
			}
		}
	}

	if edit.SelectStart == edit.SelectEnd {
		drawEolCursor := func() {
			cursor_pos := d.CursorPos

			var cursor rect.Rect
			if edit.Flags&EditIbeamCursor != 0 {
				cursor.W = int(d.Scaling)
				if cursor.W <= 0 {
					cursor.W = 1
				}
			} else {
				cursor.W = FontWidth(font, "i")
			}
			cursor.H = row_height
			cursor.X = area.X + cursor_pos.X - edit.Scrollbar.X
			cursor.Y = area.Y + cursor_pos.Y + row_height/2.0 - cursor.H/2.0
			cursor.Y -= edit.Scrollbar.Y
			out.FillRect(cursor, 0, cursor_color)
		}

		if edit.Styler != nil {
			pos = edit.editDrawTextStyled(out, style, pos, x_margin, edit.Buffer[:edit.Cursor], 0, row_height, font, background_color, text_color, false)
		} else {
			pos = edit.editDrawTextUnstyled(out, style, pos, x_margin, edit.Buffer[:edit.Cursor], 0, row_height, font, background_color, text_color, false)
		}
		d.CursorPos = pos.Sub(startPos)
		if edit.Active && d.hasInput {
			if edit.Cursor < len(edit.Buffer) {
				cursorChar := edit.Buffer[edit.Cursor]

				if cursorChar == '\n' || cursorChar == '\r' || cursorChar == '\t' || edit.Flags&EditIbeamCursor != 0 {
					if edit.Styler != nil {
						pos = edit.editDrawTextStyled(out, style, pos, x_margin, edit.Buffer[edit.Cursor:edit.Cursor+1], edit.Cursor, row_height, font, background_color, text_color, true)
					} else {
						pos = edit.editDrawTextUnstyled(out, style, pos, x_margin, edit.Buffer[edit.Cursor:edit.Cursor+1], edit.Cursor, row_height, font, background_color, text_color, true)
					}
					drawEolCursor()
				} else {
					if edit.Styler != nil {
						pos = edit.editDrawTextStyled(out, style, pos, x_margin, edit.Buffer[edit.Cursor:edit.Cursor+1], edit.Cursor, row_height, font, cursor_color, cursor_text_color, true)
					} else {
						pos = edit.editDrawTextUnstyled(out, style, pos, x_margin, edit.Buffer[edit.Cursor:edit.Cursor+1], edit.Cursor, row_height, font, cursor_color, cursor_text_color, true)
					}
				}
				if edit.Styler != nil {
					pos = edit.editDrawTextStyled(out, style, pos, x_margin, edit.Buffer[edit.Cursor+1:], edit.Cursor+1, row_height, font, background_color, text_color, false)
				} else {
					pos = edit.editDrawTextUnstyled(out, style, pos, x_margin, edit.Buffer[edit.Cursor+1:], edit.Cursor+1, row_height, font, background_color, text_color, false)
				}
			} else {
				drawEolCursor()
			}
		} else if edit.Cursor < len(edit.Buffer) {
			if edit.Styler != nil {
				pos = edit.editDrawTextStyled(out, style, pos, x_margin, edit.Buffer[edit.Cursor:], edit.Cursor, row_height, font, background_color, text_color, false)
			} else {
				pos = edit.editDrawTextUnstyled(out, style, pos, x_margin, edit.Buffer[edit.Cursor:], edit.Cursor, row_height, font, background_color, text_color, false)
			}
		}
	} else {

		if selection_begin > 0 {

			if edit.Styler != nil {
				pos = edit.editDrawTextStyled(out, style, pos, x_margin, edit.Buffer[:selection_begin], 0, row_height, font, background_color, text_color, false)
			} else {
				pos = edit.editDrawTextUnstyled(out, style, pos, x_margin, edit.Buffer[:selection_begin], 0, row_height, font, background_color, text_color, false)
			}
		}

		if selection_begin == edit.SelectEnd {
			d.CursorPos = pos.Sub(startPos)
		}

		if edit.Styler != nil {
			pos = edit.editDrawTextStyled(out, style, pos, x_margin, edit.Buffer[selection_begin:selection_end], selection_begin, row_height, font, sel_background_color, sel_text_color, true)
		} else {
			pos = edit.editDrawTextUnstyled(out, style, pos, x_margin, edit.Buffer[selection_begin:selection_end], selection_begin, row_height, font, sel_background_color, sel_text_color, true)
		}

		if selection_begin != edit.SelectEnd {
			d.CursorPos = pos.Sub(startPos)
		}

		if selection_end < len(edit.Buffer) {
			if edit.Styler != nil {
				pos = edit.editDrawTextStyled(out, style, pos, x_margin, edit.Buffer[selection_end:], selection_end, row_height, font, background_color, text_color, false)
			} else {
				pos = edit.editDrawTextUnstyled(out, style, pos, x_margin, edit.Buffer[selection_end:], selection_end, row_height, font, background_color, text_color, false)
			}
		}
	}
	d.TextSize = pos.Sub(startPos)

	for i := range edit.drawchunks {
		edit.drawchunks[i].X -= area.X
		edit.drawchunks[i].Y -= area.Y

	}

	out.PushScissor(old_clip)
	return
}

func (edit *TextEditor) editDrawTextStyled(out *command.Buffer, style *nstyle.Edit, pos image.Point, x_margin int, text []rune, textOffset int, row_height int, f font.Face, background color.RGBA, foreground color.RGBA, is_selected bool) (posOut image.Point) {
	if len(text) == 0 {
		return pos
	}

	styleRanges := edit.Styler.StyleRanges()

	currentStyleRange := styleRanges[0]
	currentStyleIndex := 0

	for currentStyleIndex, currentStyleRange = range styleRanges {
		if textOffset >= currentStyleRange.Start && textOffset <= currentStyleRange.End {
			break
		}
	}

	textLen := len(text)
	posOut = pos
	currentStyleRange.Start = textOffset
	i := 0
	for {
		chars_in_range := currentStyleRange.End - currentStyleRange.Start + 1
		if chars_in_range+i >= textLen {
			chars_in_range = textLen - i
		}
		textpart := text[i : i+chars_in_range]

		fgcolor := edit.Theme.MarkDownTheme[currentStyleRange.Style].Color
		f = edit.Theme.MdFonts[edit.Theme.MarkDownTheme[currentStyleRange.Style].FontStyle]
		posOut = edit.editDrawText(out, style, posOut, x_margin, textpart, textOffset+i, row_height, f, background, fgcolor, is_selected, currentStyleRange.Style)
		i += chars_in_range
		if i < textLen {
			currentStyleIndex++
			currentStyleRange = styleRanges[currentStyleIndex]
		} else {
			break
		}
	}
	return posOut
}

func (edit *TextEditor) editDrawTextUnstyled(out *command.Buffer, style *nstyle.Edit, pos image.Point, x_margin int, text []rune, textOffset int, row_height int, f font.Face, background color.RGBA, foreground color.RGBA, is_selected bool) (posOut image.Point) {
	if len(text) == 0 {
		return pos
	}
	var line_offset int = 0
	var line_count int = 0
	var txt textWidget
	txt.Background = background
	txt.Text = foreground

	pos_x, pos_y := pos.X, pos.Y
	start := 0

	d := font.Drawer{Face: f}

	tabsz := d.MeasureString(" ").Ceil() * tabSizeInSpaces
	pwsz := d.MeasureString("*").Ceil()

	measureText := func(start, end int) int {
		if edit.PasswordChar != 0 {
			return pwsz * (end - start)
		}

		return d.MeasureString(string(text[start:end])).Ceil()
	}

	getText := func(start, end int) string {
		if edit.PasswordChar != 0 {
			n := end - start
			if n >= len(edit.password) {
				edit.password = make([]rune, n)
				for i := range edit.password {
					edit.password[i] = edit.PasswordChar
				}
			}
			return string(edit.password[:n])
		}
		return string(text[start:end])
	}

	flushLine := func(index int) rect.Rect {

		var lblrect rect.Rect
		lblrect.Y = pos_y + line_offset
		lblrect.H = row_height
		lblrect.W = nk_null_rect.W
		lblrect.X = pos_x

		if is_selected { 
			if index == len(text) || (index == start && start == 0) {
				lblrect.W = measureText(start, index)
			}
			out.FillRect(lblrect, 0, background)
		}
		edit.drawchunks = append(edit.drawchunks, drawchunk{lblrect, start + textOffset, index + textOffset})
		widgetText(out, lblrect, getText(start, index), &txt, "LC", f)

		pos_x = x_margin

		return lblrect
	}

	flushTab := func(index int) rect.Rect {
		var lblrect rect.Rect
		lblrect.Y = pos_y + line_offset
		lblrect.H = row_height
		lblrect.W = measureText(start, index)
		lblrect.X = pos_x

		lblrect.W = int(math.Floor(float64(lblrect.X+lblrect.W-x_margin)/float64(tabsz))+1)*tabsz + x_margin - lblrect.X

		if is_selected {
			out.FillRect(lblrect, 0, background)
		}
		edit.drawchunks = append(edit.drawchunks, drawchunk{lblrect, start + textOffset, index + textOffset})
		widgetText(out, lblrect, getText(start, index), &txt, "LC", f)

		pos_x += lblrect.W

		return lblrect
	}

	pos_in_line := 0
	for index, glyph := range text {
		pos_in_line++
		switch glyph {
		case '\t':
			flushTab(index)
			start = index + 1
		case '\n':
			flushLine(index)
			line_count++
			start = index + 1
			line_offset += row_height
			pos_in_line = 0
		case '\r':
			flushLine(index)
			line_count++
			start = index + 1
			line_offset += row_height
			pos_in_line = 0

		}
	}

	if start >= len(text) {
		return image.Point{pos_x, pos_y + line_offset}
	}

	lblrect := flushLine(len(text))
	lblrect.W = measureText(start, len(text))

	return image.Point{lblrect.X + lblrect.W, lblrect.Y}
}

func runeSliceEquals(a, b []rune) bool {
	if len(a) != len(b) {
		return false
	}
	for i := range a {
		if a[i] != b[i] {
			return false
		}
	}
	return true
}

var clipboardModifier = func() key.Modifiers {
	if runtime.GOOS == "darwin" {
		return key.ModMeta
	}
	return key.ModControl
}()

func (edit *TextEditor) popupFind() {
	if edit.Flags&EditMultiline == 0 {
		return
	}
	var searchEd TextEditor
	searchEd.Flags = EditSigEnter | EditClipboard | EditSelectable
	searchEd.Buffer = append(searchEd.Buffer[:0], edit.needle...)
	searchEd.SelectStart = 0
	searchEd.SelectEnd = len(searchEd.Buffer)
	searchEd.Cursor = searchEd.SelectEnd
	searchEd.Active = true

	edit.SelectEnd = edit.SelectStart
	edit.Cursor = edit.SelectStart

	edit.win.Master().PopupOpen("Search...", WindowTitle|WindowNoScrollbar|WindowMovable|WindowBorder|WindowDynamic, rect.Rect{100, 100, 400, 500}, true, func(w *Window) {
		w.Row(30).Static()
		w.LayoutFitWidth(0, 30)
		w.Label("Search: ", "LC")
		w.LayoutSetWidth(150)
		ev := searchEd.Edit(w)
		if ev&EditCommitted != 0 {
			edit.Active = true
			w.Close()
		}
		w.LayoutSetWidth(100)
		if w.ButtonText("Done") {
			edit.Active = true
			w.Close()
		}
		kbd := &w.Input().Keyboard
		for _, k := range kbd.Keys {
			switch {
			case k.Modifiers == clipboardModifier && k.Code == key.CodeG:
				edit.lookForward(true)
			case k.Modifiers == 0 && k.Code == key.CodeEscape:
				edit.SelectEnd = edit.SelectStart
				edit.Cursor = edit.SelectStart
				edit.Active = true
				w.Close()
			}
		}
		if !runeSliceEquals(searchEd.Buffer, edit.needle) {
			edit.needle = append(edit.needle[:0], searchEd.Buffer...)
			edit.lookForward(false)
		}
	})
}

func (edit *TextEditor) lookForward(forceAdvance bool) {
	if edit.Flags&EditMultiline == 0 {
		return
	}
	if edit.hasSelection() {
		if forceAdvance {
			edit.SelectStart = edit.SelectEnd
		} else {
			edit.SelectEnd = edit.SelectStart
		}
		if edit.SelectEnd >= 0 {
			edit.Cursor = edit.SelectEnd
		}
	}
	for start := edit.Cursor; start < len(edit.Buffer); start++ {
		found := true
		for i := 0; i < len(edit.needle); i++ {
			if edit.needle[i] != edit.Buffer[start+i] {
				found = false
				break
			}
		}
		if found {
			edit.SelectStart = start
			edit.SelectEnd = start + len(edit.needle)
			edit.Cursor = edit.SelectEnd
			edit.CursorFollow = true
			return
		}
	}
	edit.SelectStart = 0
	edit.SelectEnd = 0
	edit.Cursor = 0
	edit.CursorFollow = true
}

func (edit *TextEditor) Edit(win *Window) EditEvents {
	edit.init(win)
	if edit.Maxlen > 0 {
		if len(edit.Buffer) > edit.Maxlen {
			edit.Buffer = edit.Buffer[:edit.Maxlen]
		}
	}

	if edit.Flags&EditNoCursor != 0 {
		edit.Cursor = len(edit.Buffer)
	}
	if edit.Flags&EditSelectable == 0 {
		edit.SelectStart = edit.Cursor
		edit.SelectEnd = edit.Cursor
	}

	var bounds rect.Rect

	style := &edit.win.ctx.Style
	widget_state, bounds, _ := edit.win.widget()
	if !widget_state {
		return 0
	}
	in := edit.win.inputMaybe(widget_state)

	var cut, copy, paste bool

	if w := win.ContextualOpen(0, image.Point{}, bounds, nil); w != nil {
		w.Row(20).Dynamic(1)
		visible := false
		if edit.Flags&EditClipboard != 0 {
			visible = true
			if w.MenuItem(label.TA("Cut", "LC")) {
				cut = true
			}
			if w.MenuItem(label.TA("Copy", "LC")) {
				copy = true
			}
			if w.MenuItem(label.TA("Paste", "LC")) {
				paste = true
			}
		}
		if edit.Flags&EditMultiline != 0 {
			visible = true
			if w.MenuItem(label.TA("Find...", "LC")) {
				edit.popupFind()
			}
		}
		if !visible {
			w.Close()
		}
	}

	ev := edit.doEdit(bounds, &style.Edit, in, cut, copy, paste)
	return ev
}

func (edit *TextEditor) UpdateImageCache() {
	styleRanges := edit.Styler.StyleRanges()
	edit.imageCache = make(map[string]*image.RGBA)

	for _, currentStyleRange := range styleRanges {
		if currentStyleRange.Style == mdstyle.MdStyleImageUrl || currentStyleRange.Style == mdstyle.MdStyleLinkUrl {
			filename := strings.ReplaceAll(string(edit.Buffer[currentStyleRange.Start:currentStyleRange.End+1]), "\r", "")
			img, err := LoadImage(filename)
			fmt.Printf("Image: '%s'", filename)
			if err == nil {
				edit.imageCache[filename] = img
				fmt.Println(" : OK")
			} else {
				fmt.Printf(" : FAIL : %s\n", err)
			}
		}
	}

	fmt.Printf("%v", edit.imageCache)
}
package nucular

import (
	"fmt"
	"image"
	"image/color"
	"log"
	"math"
	"path/filepath"
	"runtime"
	"strings"
	"time"
	"unicode"
	"unicode/utf8"

	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/mouse"

	"golang.org/x/image/font"
	"golang.org/x/image/math/fixed"

	"github.com/renerocksai/mdstyle"
	"github.com/renerocksai/nucular/clipboard"
	"github.com/renerocksai/nucular/command"
	"github.com/renerocksai/nucular/label"
	"github.com/renerocksai/nucular/rect"
	nstyle "github.com/renerocksai/nucular/style"
	"github.com/renerocksai/nucular/themes"
)

///////////////////////////////////////////////////////////////////////////////////
// TEXT WIDGETS
///////////////////////////////////////////////////////////////////////////////////

const (
	tabSizeInSpaces = 8
)

type textWidget struct {
	Padding    image.Point
	Background color.RGBA
	Text       color.RGBA
}

func textClamp(f font.Face, text []rune, space int) []rune {
	text_width := 0
	for i, ch := range text {
		_, _, _, xwfixed, _ := f.Glyph(fixed.P(0, 0), ch)
		xw := xwfixed.Ceil()
		if text_width+xw >= space {
			return text[:i]
		}
		text_width += xw
	}
	return text
}

func widgetText(o *command.Buffer, b rect.Rect, str string, t *textWidget, a label.Align, f font.Face) {
	b.H = max(b.H, 2*t.Padding.Y)
	lblrect := rect.Rect{X: 0, W: 0, Y: b.Y + t.Padding.Y, H: b.H - 2*t.Padding.Y}

	/* align in x-axis */
	switch a[0] {
	case 'L':
		lblrect.X = b.X + t.Padding.X
		lblrect.W = max(0, b.W-2*t.Padding.X)
	case 'C':
		text_width := FontWidth(f, str)
		text_width += (2.0 * t.Padding.X)
		lblrect.W = max(1, 2*t.Padding.X+text_width)
		lblrect.X = (b.X + t.Padding.X + ((b.W-2*t.Padding.X)-lblrect.W)/2)
		lblrect.X = max(b.X+t.Padding.X, lblrect.X)
		lblrect.W = min(b.X+b.W, lblrect.X+lblrect.W)
		if lblrect.W >= lblrect.X {
			lblrect.W -= lblrect.X
		}
	case 'R':
		text_width := FontWidth(f, str)
		text_width += (2.0 * t.Padding.X)
		lblrect.X = max(b.X+t.Padding.X, (b.X+b.W)-(2*t.Padding.X+text_width))
		lblrect.W = text_width + 2*t.Padding.X
	default:
		panic("unsupported alignment")
	}

	/* align in y-axis */
	if len(a) >= 2 {
		switch a[1] {
		case 'C':
			lblrect.Y = b.Y + b.H/2.0 - FontHeight(f)/2.0
		case 'B':
			lblrect.Y = b.Y + b.H - FontHeight(f)
		}
	}
	if lblrect.H < FontHeight(f)*2 {
		lblrect.H = FontHeight(f) * 2
	}

	o.DrawText(lblrect, str, f, t.Text)
}

func widgetTextWithBackground(o *command.Buffer, b rect.Rect, str string, t *textWidget, a label.Align, f font.Face) {
	b.H = max(b.H, 2*t.Padding.Y)
	lblrect := rect.Rect{X: 0, W: 0, Y: b.Y + t.Padding.Y, H: b.H - 2*t.Padding.Y}

	/* align in x-axis */
	switch a[0] {
	case 'L':
		lblrect.X = b.X + t.Padding.X
		lblrect.W = max(0, b.W-2*t.Padding.X)
	case 'C':
		text_width := FontWidth(f, str)
		text_width += (2.0 * t.Padding.X)
		lblrect.W = max(1, 2*t.Padding.X+text_width)
		lblrect.X = (b.X + t.Padding.X + ((b.W-2*t.Padding.X)-lblrect.W)/2)
		lblrect.X = max(b.X+t.Padding.X, lblrect.X)
		lblrect.W = min(b.X+b.W, lblrect.X+lblrect.W)
		if lblrect.W >= lblrect.X {
			lblrect.W -= lblrect.X
		}
	case 'R':
		text_width := FontWidth(f, str)
		text_width += (2.0 * t.Padding.X)
		lblrect.X = max(b.X+t.Padding.X, (b.X+b.W)-(2*t.Padding.X+text_width))
		lblrect.W = text_width + 2*t.Padding.X
	default:
		panic("unsupported alignment")
	}

	/* align in y-axis */
	if len(a) >= 2 {
		switch a[1] {
		case 'C':
			lblrect.Y = b.Y + b.H/2.0 - FontHeight(f)/2.0
		case 'B':
			lblrect.Y = b.Y + b.H - FontHeight(f)
		}
	}
	lblrect.H = FontHeight(f)

	o.FillRect(lblrect, 0, t.Background)
	o.DrawText(lblrect, str, f, t.Text)
}

func widgetTextWrap(o *command.Buffer, b rect.Rect, str []rune, t *textWidget, f font.Face) {
	var done int = 0
	var line rect.Rect
	var text textWidget

	text.Padding = image.Point{0, 0}
	text.Background = t.Background
	text.Text = t.Text

	b.W = max(b.W, 2*t.Padding.X)
	b.H = max(b.H, 2*t.Padding.Y)
	b.H = b.H - 2*t.Padding.Y

	line.X = b.X + t.Padding.X
	line.Y = b.Y + t.Padding.Y
	line.W = b.W - 2*t.Padding.X
	line.H = 2*t.Padding.Y + FontHeight(f)

	fitting := textClamp(f, str, line.W)
	for done < len(str) {
		if len(fitting) == 0 || line.Y+line.H >= (b.Y+b.H) {
			break
		}
		widgetText(o, line, string(fitting), &text, "LC", f)
		done += len(fitting)
		line.Y += FontHeight(f) + 2*t.Padding.Y
		fitting = textClamp(f, str[done:], line.W)
	}
}

///////////////////////////////////////////////////////////////////////////////////
// TEXT EDITOR
///////////////////////////////////////////////////////////////////////////////////

type propertyStatus int

const (
	propertyDefault = propertyStatus(iota)
	propertyEdit
	propertyDrag
)

// TextEditor stores the state of a text editor.
// To add a text editor to a window create a TextEditor object with
// &TextEditor{}, store it somewhere then in the update function call
// the Edit method passing the window to it.
type TextEditor struct {
	win            *Window
	propertyStatus propertyStatus
	Cursor         int
	Buffer         []rune
	Filter         FilterFunc
	Flags          EditFlags
	CursorFollow   bool
	Redraw         bool
	Font           font.Face

	Maxlen int

	PasswordChar rune // if non-zero all characters are displayed like this character

	Initialized            bool
	Active                 bool
	InsertMode             bool
	Scrollbar              image.Point
	SelectStart, SelectEnd int
	HasPreferredX          bool
	SingleLine             bool
	PreferredX             int
	Undo                   textUndoState

	drawchunks []drawchunk

	lastClickCoord  image.Point
	lastClickTime   time.Time
	clickCount      int
	trueSelectStart int

	needle []rune

	password []rune // support buffer for drawing PasswordChar!=0 fields

	// RENE WRAP
	WrapColumn int

	// RENE Style
	Styler mdstyle.Styler
	Theme  *themes.GuiTheme

	// RENE intelligent rendering

	/*

		RENE TODO: measure what takes so long and if we're effectifely only rendering visible lines or what's the story

		do we really need to re-flow on scrolling? probably not ; also not on cursor movement -- but fixing that won't fix edit lags


	*/

	textArea          rect.Rect
	firstVisibleLine  int
	firstVisibleIndex int
	lastVisibleLine   int
	lastVisibleIndex  int
	charSize          int

	// RENE images
	imageCache          map[string]*image.RGBA
	imagesInCurrentLine []*image.RGBA
	ShowImages          bool

	// RENE document support
	FileName     string
	FileBasename string
	Dirty        bool
}

func (te *TextEditor) SetFileName(fn string) {
	te.FileName = fn
	te.FileBasename = filepath.Base(fn)
}

type drawchunk struct {
	rect.Rect
	start, end int
}

func (ed *TextEditor) init(win *Window) {
	if ed.Filter == nil {
		ed.Filter = FilterDefault
	}
	if !ed.Initialized {
		if ed.Flags&EditMultiline != 0 {
			ed.clearState(TextEditMultiLine)
		} else {
			ed.clearState(TextEditSingleLine)
		}

	}
	if ed.win == nil || ed.win != win {
		if ed.win == nil {
			if ed.Buffer == nil {
				ed.Buffer = []rune{}
			}
			ed.Filter = nil
			ed.Cursor = 0
		}
		ed.Redraw = true
		ed.win = win
	}

	f := ed.Font
	if f == nil {
		ed.Font = win.ctx.Style.Font
		f = ed.Font
	}
	d := font.Drawer{Face: f}
	ed.charSize = d.MeasureString("M").Ceil()
}

func (ed *TextEditor) Construct(styler mdstyle.Styler, theme themes.GuiTheme) {
	ed.Styler = styler
	ed.Theme = &theme
	ed.lastVisibleIndex = 10240
}

type EditFlags int

const (
	EditDefault  EditFlags = 0
	EditReadOnly EditFlags = 1 << iota
	EditAutoSelect
	EditSigEnter
	EditNoCursor
	EditSelectable
	EditClipboard
	EditCtrlEnterNewline
	EditNoHorizontalScroll
	EditAlwaysInsertMode
	EditMultiline
	EditNeverInsertMode
	EditFocusFollowsMouse
	EditNoContextMenu
	EditIbeamCursor

	EditSimple = EditAlwaysInsertMode
	EditField  = EditSelectable | EditClipboard | EditSigEnter
	EditBox    = EditSelectable | EditMultiline | EditClipboard
)

type EditEvents int

const (
	EditActive EditEvents = 1 << iota
	EditInactive
	EditActivated
	EditDeactivated
	EditCommitted
)

type TextEditType int

const (
	TextEditSingleLine TextEditType = iota
	TextEditMultiLine
)

type textUndoRecord struct {
	Where        int
	InsertLength int
	DeleteLength int
	Text         []rune
}

const _TEXTEDIT_UNDOSTATECOUNT = 99

type textUndoState struct {
	UndoRec   [_TEXTEDIT_UNDOSTATECOUNT]textUndoRecord
	UndoPoint int16
	RedoPoint int16
}

func strInsertText(str []rune, pos int, runes []rune) []rune {
	if cap(str) < len(str)+len(runes) {
		newcap := (cap(str) + 1) * 2
		if newcap < len(str)+len(runes) {
			newcap = len(str) + len(runes)
		}
		newstr := make([]rune, len(str), newcap)
		copy(newstr, str)
		str = newstr
	}
	str = str[:len(str)+len(runes)]
	copy(str[pos+len(runes):], str[pos:])
	copy(str[pos:], runes)
	return str
}

func strDeleteText(s []rune, pos int, dlen int) []rune {
	copy(s[pos:], s[pos+dlen:])
	s = s[:len(s)-dlen]
	return s
}

func (s *TextEditor) hasSelection() bool {
	return s.SelectStart != s.SelectEnd
}

func (edit *TextEditor) locateCoord(p image.Point, font font.Face, row_height int) int {
	x, y := p.X, p.Y
	//	fmt.Printf("x: %d  -  y: %d\n", x, y)

	var drawchunk *drawchunk

	for i := range edit.drawchunks {
		min := edit.drawchunks[i].Min()
		max := edit.drawchunks[i].Max()
		if min.Y <= y && y <= max.Y {
			if min.X <= x && x <= max.X {
				drawchunk = &edit.drawchunks[i]
				//				fmt.Printf("Found: %v: %d,%d - %d,%d\n", drawchunk, min.X, min.Y, max.X, max.Y)
			}
		}
	}

	if drawchunk == nil {
		return len(edit.Buffer)
	}

	curx := drawchunk.X
	for i := drawchunk.start; i < drawchunk.end && i < len(edit.Buffer); i++ {
		curx += FontWidth(font, string(edit.Buffer[i:i+1]))
		if curx > x {
			return i
		}
	}

	if drawchunk.end >= len(edit.Buffer) {
		return len(edit.Buffer)
	}

	return drawchunk.end
}

func (edit *TextEditor) indexToCoord(index int, font font.Face, row_height int) image.Point {
	var drawchunk *drawchunk
	//	fmt.Printf("index: %d\n", index)

	for i := range edit.drawchunks {
		if edit.drawchunks[i].start > index {
			if i == 0 {
				drawchunk = &edit.drawchunks[0]
			} else {
				drawchunk = &edit.drawchunks[i-1]
			}
			break
		}
	}

	if drawchunk == nil {
		if len(edit.drawchunks) == 0 {
			return image.Point{}
		}
		drawchunk = &edit.drawchunks[len(edit.drawchunks)-1]
	}

	x := drawchunk.X
	for i := drawchunk.start; i < drawchunk.end && i < len(edit.Buffer); i++ {
		if i >= index {
			break
		}
		x += FontWidth(font, string(edit.Buffer[i:i+1]))
	}
	if index >= len(edit.Buffer) && len(edit.Buffer) > 0 && edit.Buffer[len(edit.Buffer)-1] == '\n' {
		return image.Point{x, drawchunk.Y + drawchunk.H + drawchunk.H/2}
	}
	// RENE WRAP
	if index >= len(edit.Buffer) && len(edit.Buffer) > 0 && edit.Buffer[len(edit.Buffer)-1] == '\r' {
		return image.Point{x, drawchunk.Y + drawchunk.H + drawchunk.H/2}
	}
	return image.Point{x, drawchunk.Y + drawchunk.H/2}
}

func (state *TextEditor) doubleClick(coord image.Point) bool {
	abs := func(x int) int {
		if x < 0 {
			return -x
		}
		return x
	}
	r := time.Since(state.lastClickTime) < 200*time.Millisecond && abs(state.lastClickCoord.X-coord.X) < 5 && abs(state.lastClickCoord.Y-coord.Y) < 5
	state.lastClickCoord = coord
	state.lastClickTime = time.Now()
	return r

}

func (state *TextEditor) click(coord image.Point, font font.Face, row_height int) {
	/* API click: on mouse down, move the cursor to the clicked location,
	 * and reset the selection */
	state.Cursor = state.locateCoord(coord, font, row_height)

	state.SelectStart = state.Cursor
	state.trueSelectStart = state.Cursor
	state.SelectEnd = state.Cursor
	state.HasPreferredX = false

	switch state.clickCount {
	case 2:
		state.selectWord(state.SelectEnd)
	case 3:
		state.selectLine(state.SelectEnd)
	}
}

func (state *TextEditor) drag(coord image.Point, font font.Face, row_height int) {
	/* API drag: on mouse drag, move the cursor and selection endpoint
	 * to the clicked location */
	var p int = state.locateCoord(coord, font, row_height)
	if state.SelectStart == state.SelectEnd {
		state.SelectStart = state.Cursor
		state.trueSelectStart = p
	}
	state.SelectEnd = p
	state.Cursor = state.SelectEnd

	switch state.clickCount {
	case 2:
		state.selectWord(p)
	case 3:
		state.selectLine(p)
	}
}

func (state *TextEditor) selectWord(end int) {
	state.SelectStart = state.trueSelectStart
	state.SelectEnd = end
	state.sortselection()
	state.SelectStart = state.towd(state.SelectStart, -1, false)
	state.SelectEnd = state.towd(state.SelectEnd, +1, true)
}

func (state *TextEditor) selectLine(end int) {
	state.SelectStart = state.trueSelectStart
	state.SelectEnd = end
	state.sortselection()
	state.SelectStart = state.tonl(state.SelectStart-1, -1)
	state.SelectEnd = state.tonl(state.SelectEnd, +1)
}

func (state *TextEditor) clamp() {
	/* make the selection/cursor state valid if client altered the string */
	if state.hasSelection() {
		if state.SelectStart > len(state.Buffer) {
			state.SelectStart = len(state.Buffer)
		}
		if state.SelectEnd > len(state.Buffer) {
			state.SelectEnd = len(state.Buffer)
		}

		/* if clamping forced them to be equal, move the cursor to match */
		if state.SelectStart == state.SelectEnd {
			state.Cursor = state.SelectStart
		}
	}

	if state.Cursor > len(state.Buffer) {
		state.Cursor = len(state.Buffer)
	}
}

// Deletes a chunk of text in the editor.
func (edit *TextEditor) Delete(where int, len int) {
	/* delete characters while updating undo */
	edit.makeundoDelete(where, len)

	edit.Buffer = strDeleteText(edit.Buffer, where, len)
	edit.HasPreferredX = false
}

// Deletes selection.
func (edit *TextEditor) DeleteSelection() {
	/* delete the section */
	edit.clamp()

	if edit.hasSelection() {
		if edit.SelectStart < edit.SelectEnd {
			edit.Delete(edit.SelectStart, edit.SelectEnd-edit.SelectStart)
			edit.Cursor = edit.SelectStart
			edit.SelectEnd = edit.Cursor
		} else {
			edit.Delete(edit.SelectEnd, edit.SelectStart-edit.SelectEnd)
			edit.Cursor = edit.SelectEnd
			edit.SelectStart = edit.Cursor
		}

		edit.HasPreferredX = false
	}
}

func (state *TextEditor) sortselection() {
	/* canonicalize the selection so start <= end */
	if state.SelectEnd < state.SelectStart {
		var temp int = state.SelectEnd
		state.SelectEnd = state.SelectStart
		state.SelectStart = temp
	}
}

func (state *TextEditor) moveToFirst() {
	/* move cursor to first character of selection */
	if state.hasSelection() {
		state.sortselection()
		state.Cursor = state.SelectStart
		state.SelectEnd = state.SelectStart
		state.HasPreferredX = false
	}
}

func (state *TextEditor) moveToLast() {
	/* move cursor to last character of selection */
	if state.hasSelection() {
		state.sortselection()
		state.clamp()
		state.Cursor = state.SelectEnd
		state.SelectStart = state.SelectEnd
		state.HasPreferredX = false
	}
}

// Moves to the beginning or end of a line
func (state *TextEditor) tonl(start int, dir int) int {
	sz := len(state.Buffer)

	i := start
	if i < 0 {
		return 0
	}
	if i >= sz {
		i = sz - 1
	}
	for ; (i >= 0) && (i < sz); i += dir {
		c := state.Buffer[i]

		if c == '\n' {
			if dir >= 0 {
				return i
			} else {
				return i + 1
			}
		}
	}
	if dir < 0 {
		return 0
	} else {
		return sz
	}
}

// Moves to the beginning or end of an alphanumerically delimited word
func (state *TextEditor) towd(start int, dir int, dontForceAdvance bool) int {
	first := (dir < 0)
	notfirst := !first
	var i int
	for i = start; (i >= 0) && (i < len(state.Buffer)); i += dir {
		c := state.Buffer[i]
		if !(unicode.IsLetter(c) || unicode.IsDigit(c) || (c == '_')) {
			if !first && !dontForceAdvance {
				i++
			}
			break
		}
		first = notfirst
	}
	if i < 0 {
		i = 0
	}
	return i
}

// Moves to the beginning or end of a space delimited word
func (state *TextEditor) tospc(start int, dir int) int {
	return state.tof(start, dir, unicode.IsSpace)
}

// Moves to the first position where f returns true
func (state *TextEditor) tof(start int, dir int, f func(rune) bool) int {
	first := (dir < 0)
	notfirst := !first
	var i int
	for i = start; (i >= 0) && (i < len(state.Buffer)); i += dir {
		c := state.Buffer[i]
		if f(c) {
			if !first {
				i++
			}
			break
		}
		first = notfirst
	}
	if i < 0 {
		i = 0
	}
	return i
}

// Moves to the beginning or end of something that looks like a file path
func (state *TextEditor) tofp(start int, dir int) int {
	first := (dir < 0)
	notfirst := !first
	var i int
	for i = start; (i >= 0) && (i < len(state.Buffer)); i += dir {
		c := state.Buffer[i]
		if !(unicode.IsLetter(c) || unicode.IsDigit(c) || (c == '_') || (c == '-') || (c == '+') || (c == '/') || (c == '=') || (c == '~') || (c == '!') || (c == ':') || (c == ',') || (c == '.')) {
			if !first {
				i++
			}
			break
		}
		first = notfirst
	}
	if i < 0 {
		i = 0
	}
	return i

}

func (state *TextEditor) prepSelectionAtCursor() {
	/* update selection and cursor to match each other */
	if !state.hasSelection() {
		state.SelectEnd = state.Cursor
		state.SelectStart = state.SelectEnd
	} else {
		state.Cursor = state.SelectEnd
	}
}

func (edit *TextEditor) Cut() int {
	if edit.Flags&EditReadOnly != 0 {
		return 0
	}
	/* API cut: delete selection */
	if edit.hasSelection() {
		edit.DeleteSelection() /* implicitly clamps */
		edit.HasPreferredX = false
		edit.Dirty = true
		return 1
	}

	return 0
}

// Paste from clipboard
func (edit *TextEditor) Paste(ctext string) {
	if edit.Flags&EditReadOnly != 0 {
		return
	}

	/* if there's a selection, the paste should delete it */
	edit.clamp()

	edit.DeleteSelection()

	text := []rune(ctext)

	edit.Buffer = strInsertText(edit.Buffer, edit.Cursor, text)

	edit.makeundoInsert(edit.Cursor, len(text))
	edit.Cursor += len(text)
	edit.HasPreferredX = false
	edit.Dirty = true
}

// Wrap will wrap one or more lines of text at the given length.
// If limit is less than 1, the string remains unwrapped.
func (edit *TextEditor) ReflowNotGoodForBigFiles(onlystyle bool) {
	if edit.WrapColumn <= 0 {
		return
	}
	if edit.SingleLine {
		return
	}
	_tstart := time.Now()
	if !onlystyle {

		var ret string

		s := string(edit.Buffer)
		num_breaks_existing_before_cursor := strings.Count(s[:edit.Cursor], "\r")

		s = strings.ReplaceAll(s, "\r", "")
		for _, str := range strings.Split(s, "\n") {
			ret += edit.WrapLine(str, edit.WrapColumn) + "\n"
		}

		edit.Buffer = []rune(ret)

		num_breaks_new_before_cursor := strings.Count(ret[:edit.Cursor], "\r")
		if num_breaks_new_before_cursor > num_breaks_existing_before_cursor {
			edit.Cursor += num_breaks_new_before_cursor - num_breaks_existing_before_cursor
		}
	}

	fmt.Printf("%s - reflow\n", time.Since(_tstart))

	_tstart = time.Now()
	if edit.Styler != nil {
		styleDelta := 10240
		styleFrom := edit.firstVisibleIndex - styleDelta
		if styleFrom < 0 {
			styleFrom = 0
		}
		styleTo := edit.lastVisibleIndex + styleDelta
		if styleTo >= len(edit.Buffer) {
			styleTo = len(edit.Buffer) - 1
		}
		edit.Styler.Style(edit.Buffer, styleFrom, styleTo)
	}
	fmt.Printf("%s - styler\n\n\n", time.Since(_tstart))
}

// line will wrap a single line of text at the given length.
// If limit is less than 1, the string remains unwrapped.
func (edit *TextEditor) WrapLine(s string, limit int) string {
	if limit < 1 || utf8.RuneCountInString(s) < limit+1 {
		return s
	}

	breakpoints := " -"

	// Find the index of the last breakpoint within the limit.
	i := strings.LastIndexAny(s[:limit+1], breakpoints)

	// Can't wrap within the limit, wrap at the next breakpoint instead.
	if i < 0 {
		i = limit
	}

	// Recurse until we have nothing left to do.
	return s[:i+1] + "\r" + edit.WrapLine(s[i+1:], limit)
}

func (edit *TextEditor) Reflow(onlystyle bool) {
	// RENE WRAP
	_tstart := time.Now()
	if edit.WrapColumn <= 0 {
		return
	}
	if edit.SingleLine {
		return
	}
	if !onlystyle {
		newBuffer := make([]rune, 2*len(edit.Buffer))
		//	fmt.Printf("%s - newBuffer\n", time.Since(_tstart))
		//	_tstart = time.Now()

		dest_index := 0
		num_breaks_existing_before_cursor := 0
		num_breaks_new_before_cursor := 0
		pos_in_line := 0
		srcindex := 0
		for srcindex = 0; srcindex < len(edit.Buffer); {
			r := edit.Buffer[srcindex]
			if r == '\r' {
				if srcindex <= edit.Cursor {
					num_breaks_existing_before_cursor++
				}
				srcindex++
			}

			if r == '\n' {
				pos_in_line = 0
				newBuffer[dest_index] = r
				dest_index++
				srcindex++
			} else {
				current_word_length := edit.getWordLen(srcindex)
				if current_word_length+pos_in_line > edit.WrapColumn { // pos_index = 1 at start, wordlen is always >= 1

					if pos_in_line != 0 {
						newBuffer[dest_index] = '\r'
						dest_index++
						// we need to wrap here
						if srcindex <= edit.Cursor {
							num_breaks_new_before_cursor++
						}
					}
					// now check if the new word fits into the new line
					pos_in_line = 0
					if current_word_length >= edit.WrapColumn {
						//					log.Printf("Need to wrap within word b/c current_word_length = %d", current_word_length)
						src_advance := edit.copyWithoutSoftbreaks(srcindex, edit.WrapColumn, newBuffer, dest_index)
						dest_index += edit.WrapColumn
						srcindex += src_advance
						newBuffer[dest_index] = '\r'
						// we need to wrap here
						if srcindex <= edit.Cursor {
							num_breaks_new_before_cursor++
						}
						dest_index++
						continue
					}
				} else {
					//				log.Printf("NO wrap b/c current_word_length = %d", current_word_length)
					src_advance := edit.copyWithoutSoftbreaks(srcindex, current_word_length, newBuffer, dest_index)
					dest_index += current_word_length
					srcindex += src_advance
					pos_in_line += current_word_length
				}
			}
		}

		if num_breaks_new_before_cursor > num_breaks_existing_before_cursor {
			edit.Cursor += num_breaks_new_before_cursor - num_breaks_existing_before_cursor
		}
		edit.Buffer = newBuffer[:dest_index]
		fmt.Printf("%s - reflow\n", time.Since(_tstart))
	}
	// after re-flowing, we must also re-style

	_tstart = time.Now()
	if edit.Styler != nil {
		styleDelta := 10240
		styleFrom := edit.firstVisibleIndex - styleDelta
		if styleFrom < 0 {
			styleFrom = 0
		}
		styleTo := edit.lastVisibleIndex + styleDelta
		if styleTo >= len(edit.Buffer) {
			styleTo = len(edit.Buffer) - 1
		}
		edit.Styler.Style(edit.Buffer, styleFrom, styleTo)
	}
	fmt.Printf("%s - styler\n\n\n", time.Since(_tstart))
}

func (edit *TextEditor) copyWithoutSoftbreaks(srcindex int, length int, dest []rune, dest_index int) int {
	i := 0
	L := len(edit.Buffer)
	num_copied := 0
	for i = 0; num_copied < length; {
		if srcindex+i >= L {
			break
		}
		if edit.Buffer[srcindex+i] != '\r' {
			dest[dest_index] = edit.Buffer[srcindex+i]
			dest_index++
			num_copied++
		}
		i++
	}
	return i
}

func (edit *TextEditor) getWordLen(pos int) int {
	wordlength := 0
	for srcindex := pos; srcindex < len(edit.Buffer); srcindex++ {
		r := edit.Buffer[srcindex]

		// softbreaks dont count
		if r == '\r' {
			continue
		}

		if r == ' ' || r == '\n' {
			// if we have a space right at the start
			// (newlines can't be at the start because reflow has checked for that before calling us)
			if wordlength == 0 {
				wordlength = 1
			}
			break
		}
		wordlength++
	}
	return wordlength
}
func (edit *TextEditor) ReflowStatic() {
	// RENE WRAP
	if edit.WrapColumn <= 0 {
		return
	}
	if !edit.SingleLine {
		// first count how many pseudo newlines we have to insert
		pos_in_line := 0
		num_inserts := 0
		num_chars := 0
		num_existing_softbreaks := 0
		for srcindex, r := range edit.Buffer {
			if r == '\r' {
				num_existing_softbreaks++
				continue
			}
			num_chars++
			pos_in_line++
			if r == '\n' {
				pos_in_line = 1
			}
			if pos_in_line == edit.WrapColumn+1 {
				if srcindex != len(edit.Buffer)-1 {
					num_inserts++
					pos_in_line = 1
				}
			}
		}

		// so insert them
		// reflow buffer
		str := edit.Buffer
		newstr := make([]rune, num_chars+num_inserts)
		dest_index := 0
		pos_in_line = 0
		for srcindex, r := range str {
			if r == '\r' {
				continue
			}
			pos_in_line++
			if r == '\n' {
				pos_in_line = 1
			}
			if pos_in_line == edit.WrapColumn+1 {
				if srcindex != len(str)-1 {
					newstr[dest_index] = '\r'
					dest_index++
					pos_in_line = 1
				}
			}
			newstr[dest_index] = r
			dest_index++
		}
		edit.Buffer = newstr
		if num_inserts > num_existing_softbreaks {
			edit.Cursor += num_inserts - num_existing_softbreaks
		}

		// NOTE(rene) : We need to support edit.Cursor == len(edit.Buffer) + 1 for typing and backspacing to work
		// if we set the cursor to len, then the cursor is mis-placed after backspace: it lands on the last char
		// instead of 1 after the last char. This messes up everything and leads to a crash when you type one word (123),
		// then delete from the middle of the word (23), then delete until cursor pos is 0 (3), then go one right to place
		// the cursor after the last char (3_) and delete it.
		// This may probably be caused by some (delete?) code that runs after this and subtracts 1 from the cursor position.
		// So here we ensure the cursor pos is always at most 1 after the len, instead of exactly the len
		log.Printf("Cursor: %v, len buffer: %v", edit.Cursor, len(edit.Buffer))
		if edit.Cursor > len(edit.Buffer)+1 {
			// this should never happen
			edit.Cursor = len(edit.Buffer) + 1
		}
	}
}

func (edit *TextEditor) Text(text []rune) {
	if edit.Flags&EditReadOnly != 0 {
		return
	}

	for i := range text {
		/* can't add newline in single-line mode */
		if text[i] == '\n' && edit.SingleLine {
			break
		}

		/* can't add tab in single-line mode */
		if text[i] == '\t' && edit.SingleLine {
			break
		}

		/* filter incoming text */
		if edit.Filter != nil && !edit.Filter(text[i]) {
			continue
		}

		if edit.InsertMode && !edit.hasSelection() && edit.Cursor < len(edit.Buffer) {
			edit.makeundoReplace(edit.Cursor, 1, 1)
			edit.Buffer = strDeleteText(edit.Buffer, edit.Cursor, 1)
			edit.Buffer = strInsertText(edit.Buffer, edit.Cursor, text[i:i+1])
			edit.Cursor++
			edit.HasPreferredX = false
		} else {
			edit.DeleteSelection() /* implicitly clamps */
			edit.Buffer = strInsertText(edit.Buffer, edit.Cursor, text[i:i+1])
			edit.makeundoInsert(edit.Cursor, 1)
			edit.Cursor++
			edit.HasPreferredX = false
		}
	}
	edit.Dirty = true
}

func (state *TextEditor) key(e key.Event, font font.Face, row_height int) {
	readOnly := state.Flags&EditReadOnly != 0
retry:
	switch e.Code {
	case key.CodeZ:
		if readOnly {
			return
		}
		if e.Modifiers&key.ModControl != 0 {
			if e.Modifiers&key.ModShift != 0 {
				state.DoRedo()
				state.HasPreferredX = false

			} else {
				state.DoUndo()
				state.HasPreferredX = false
			}
		}

	case key.CodeK:
		if readOnly {
			return
		}
		if e.Modifiers&key.ModControl != 0 {
			state.trueSelectStart = state.Cursor
			state.selectLine(state.Cursor)
			state.DeleteSelection()
		}

	case key.CodeInsert:
		state.InsertMode = !state.InsertMode

	case key.CodeLeftArrow:
		if e.Modifiers&key.ModControl != 0 {
			if e.Modifiers&key.ModShift != 0 {
				if !state.hasSelection() {
					state.prepSelectionAtCursor()
				}
				state.Cursor = state.towd(state.Cursor-1, -1, false)
				state.SelectEnd = state.Cursor
				state.clamp()
			} else {
				if state.hasSelection() {
					state.moveToFirst()
				} else {
					state.Cursor = state.towd(state.Cursor-1, -1, false)
					state.clamp()
				}
			}
		} else {
			if e.Modifiers&key.ModShift != 0 {
				state.clamp()
				state.prepSelectionAtCursor()

				/* move selection left */
				if state.SelectEnd > 0 {
					state.SelectEnd--
				}
				state.Cursor = state.SelectEnd
				state.HasPreferredX = false
			} else {
				/* if currently there's a selection,
				 * move cursor to start of selection */
				if state.hasSelection() {
					state.moveToFirst()
				} else if state.Cursor > 0 {
					state.Cursor--
				}
				state.HasPreferredX = false
			}
		}

	case key.CodeRightArrow:
		if e.Modifiers&key.ModControl != 0 {
			if e.Modifiers&key.ModShift != 0 {
				if !state.hasSelection() {
					state.prepSelectionAtCursor()
				}
				state.Cursor = state.towd(state.Cursor, +1, false)
				state.SelectEnd = state.Cursor
				state.clamp()
			} else {
				if state.hasSelection() {
					state.moveToLast()
				} else {
					state.Cursor = state.towd(state.Cursor, +1, false)
					state.clamp()
				}
			}
		} else {
			if e.Modifiers&key.ModShift != 0 {
				state.prepSelectionAtCursor()

				/* move selection right */
				state.SelectEnd++

				state.clamp()
				state.Cursor = state.SelectEnd
				state.HasPreferredX = false
			} else {
				/* if currently there's a selection,
				 * move cursor to end of selection */
				if state.hasSelection() {
					state.moveToLast()
				} else {
					state.Cursor++
				}
				state.clamp()
				state.HasPreferredX = false
			}
		}
	case key.CodeDownArrow:
		if state.SingleLine {
			e.Code = key.CodeRightArrow
			goto retry
		}

		if e.Modifiers&key.ModShift != 0 {
			if !state.hasSelection() {
				state.prepSelectionAtCursor()
			}
		} else if state.hasSelection() {
			state.moveToLast()
		}
		state.clamp()

		p := state.indexToCoord(state.Cursor, font, row_height)
		p.Y += row_height
		if state.HasPreferredX {
			p.X = state.PreferredX
		} else {
			state.HasPreferredX = true
			state.PreferredX = p.X
		}
		state.Cursor = state.locateCoord(p, font, row_height)

		if e.Modifiers&key.ModShift != 0 {
			state.SelectEnd = state.Cursor
		}
		state.clamp()

	case key.CodeUpArrow:
		if state.SingleLine {
			e.Code = key.CodeRightArrow
			goto retry
		}

		if e.Modifiers&key.ModShift != 0 {
			if !state.hasSelection() {
				state.prepSelectionAtCursor()
			}
		} else if state.hasSelection() {
			state.moveToFirst()
		}

		state.clamp()

		p := state.indexToCoord(state.Cursor, font, row_height)
		p.Y -= row_height
		if state.HasPreferredX {
			p.X = state.PreferredX
		} else {
			state.HasPreferredX = true
			state.PreferredX = p.X
		}
		state.Cursor = state.locateCoord(p, font, row_height)
		if state.Cursor >= len(state.Buffer) {
			state.Cursor = 0
		}

		if e.Modifiers&key.ModShift != 0 {
			state.SelectEnd = state.Cursor
		}
		state.clamp()

	case key.CodePageUp:
		if state.SingleLine {
			e.Code = key.CodeRightArrow
			goto retry
		}

		if e.Modifiers&key.ModShift != 0 {
			if !state.hasSelection() {
				state.prepSelectionAtCursor()
			}
		} else if state.hasSelection() {
			state.moveToFirst()
		}

		state.clamp()

		linesUp := state.lastVisibleLine - state.firstVisibleLine
		p := state.indexToCoord(state.Cursor, font, row_height)
		p.Y -= row_height * linesUp
		if state.HasPreferredX {
			p.X = state.PreferredX
		} else {
			state.HasPreferredX = true
			state.PreferredX = p.X
		}
		state.Cursor = state.locateCoord(p, font, row_height)
		if state.Cursor >= len(state.Buffer) {
			state.Cursor = 0
		}
		if e.Modifiers&key.ModShift != 0 {
			state.SelectEnd = state.Cursor
		}
		state.clamp()

	case key.CodePageDown:
		if state.SingleLine {
			e.Code = key.CodeRightArrow
			goto retry
		}

		if e.Modifiers&key.ModShift != 0 {
			if !state.hasSelection() {
				state.prepSelectionAtCursor()
			}
			state.prepSelectionAtCursor()
		} else if state.hasSelection() {
			state.moveToFirst()
		}

		state.clamp()

		linesDown := state.lastVisibleLine - state.firstVisibleLine
		p := state.indexToCoord(state.Cursor, font, row_height)
		p.Y += row_height * linesDown
		if state.HasPreferredX {
			p.X = state.PreferredX
		} else {
			state.HasPreferredX = true
			state.PreferredX = p.X
		}
		state.Cursor = state.locateCoord(p, font, row_height)
		if state.Cursor >= len(state.Buffer) {
			state.Cursor = len(state.Buffer) - 1
		}
		if e.Modifiers&key.ModShift != 0 {
			state.SelectEnd = state.Cursor
		}
		state.clamp()

	case key.CodeDeleteForward:
		if readOnly {
			return
		}
		if state.hasSelection() {
			state.DeleteSelection()
		} else {
			if state.Cursor < len(state.Buffer) {
				state.Delete(state.Cursor, 1)
			}
		}

		state.HasPreferredX = false

	case key.CodeDeleteBackspace:
		if readOnly {
			return
		}
		switch {
		case state.hasSelection():
			state.DeleteSelection()
		case e.Modifiers&key.ModControl != 0:
			state.SelectEnd = state.Cursor
			state.SelectStart = state.towd(state.Cursor-1, -1, false)
			state.DeleteSelection()
		default:
			state.clamp()
			if state.Cursor > 0 {
				// RENE WRAP
				if state.Buffer[state.Cursor-1] == '\r' {
					state.Delete(state.Cursor-2, 2)
					//					state.Reflow()
					state.Cursor -= 3
				} else {
					state.Delete(state.Cursor-1, 1)
					//					state.Reflow()
					state.Cursor--
				}
			}

		}
		state.HasPreferredX = false

	case key.CodeHome:
		if e.Modifiers&key.ModControl != 0 {
			if e.Modifiers&key.ModShift != 0 {
				state.prepSelectionAtCursor()
				state.SelectEnd = 0
				state.Cursor = state.SelectEnd
				state.HasPreferredX = false
			} else {
				state.SelectEnd = 0
				state.SelectStart = state.SelectEnd
				state.Cursor = state.SelectStart
				state.HasPreferredX = false
			}
		} else {
			state.clamp()
			start := state.tonl(state.Cursor-1, -1)
			if e.Modifiers&key.ModShift != 0 {
				state.clamp()
				state.prepSelectionAtCursor()
				state.SelectEnd = start
				state.Cursor = state.SelectEnd
				state.HasPreferredX = false
			} else {
				state.clamp()
				state.moveToFirst()
				state.Cursor = start
				state.HasPreferredX = false
			}
		}

	case key.CodeA:
		if e.Modifiers&key.ModControl != 0 {
			// RENE select all

			// NOTE(rene): Old behaviour was: Find first char of selection, then move to beginning of line
			if true {
				state.SelectAll()
			} else {
				state.clamp()
				state.moveToFirst()
				state.Cursor = state.tonl(state.Cursor-1, -1)
				state.HasPreferredX = false
			}
		}

	case key.CodeEnd:
		if e.Modifiers&key.ModControl != 0 {
			if e.Modifiers&key.ModShift != 0 {
				state.prepSelectionAtCursor()
				state.SelectEnd = len(state.Buffer)
				state.Cursor = state.SelectEnd
				state.HasPreferredX = false
			} else {
				state.Cursor = len(state.Buffer)
				state.SelectEnd = 0
				state.SelectStart = state.SelectEnd
				state.HasPreferredX = false
			}
		} else {
			state.clamp()
			end := state.tonl(state.Cursor, +1)
			if e.Modifiers&key.ModShift != 0 {
				state.clamp()
				state.prepSelectionAtCursor()
				state.HasPreferredX = false
				state.Cursor = end
				state.SelectEnd = state.Cursor
			} else {
				state.clamp()
				state.moveToFirst()
				state.HasPreferredX = false
				state.Cursor = end
			}
		}

	case key.CodeE:
		if e.Modifiers&key.ModControl != 0 {
			end := state.tonl(state.Cursor, +1)
			state.clamp()
			state.moveToFirst()
			state.HasPreferredX = false
			state.Cursor = end
		}
	}
	//	state.Reflow()
}

func texteditFlushRedo(state *textUndoState) {
	state.RedoPoint = int16(_TEXTEDIT_UNDOSTATECOUNT)
}

func texteditDiscardUndo(state *textUndoState) {
	/* discard the oldest entry in the undo list */
	if state.UndoPoint > 0 {
		state.UndoPoint--
		copy(state.UndoRec[:], state.UndoRec[1:])
	}
}

func texteditCreateUndoRecord(state *textUndoState, numchars int) *textUndoRecord {
	/* any time we create a new undo record, we discard redo*/
	texteditFlushRedo(state)

	/* if we have no free records, we have to make room,
	 * by sliding the existing records down */
	if int(state.UndoPoint) == _TEXTEDIT_UNDOSTATECOUNT {
		texteditDiscardUndo(state)
	}

	r := &state.UndoRec[state.UndoPoint]
	state.UndoPoint++
	return r
}

func texteditCreateundo(state *textUndoState, pos int, insert_len int, delete_len int) *textUndoRecord {
	r := texteditCreateUndoRecord(state, insert_len)

	r.Where = pos
	r.InsertLength = insert_len
	r.DeleteLength = delete_len
	r.Text = nil

	return r
}

func (edit *TextEditor) DoUndo() {
	var s *textUndoState = &edit.Undo
	var u textUndoRecord
	var r *textUndoRecord
	if s.UndoPoint == 0 {
		return
	}

	/* we need to do two things: apply the undo record, and create a redo record */
	u = s.UndoRec[s.UndoPoint-1]

	r = &s.UndoRec[s.RedoPoint-1]
	r.Text = nil

	r.InsertLength = u.DeleteLength
	r.DeleteLength = u.InsertLength
	r.Where = u.Where

	if u.DeleteLength != 0 {
		r.Text = make([]rune, u.DeleteLength)
		copy(r.Text, edit.Buffer[u.Where:u.Where+u.DeleteLength])
		edit.Buffer = strDeleteText(edit.Buffer, u.Where, u.DeleteLength)
	}

	/* check type of recorded action: */
	if u.InsertLength != 0 {
		/* easy case: was a deletion, so we need to insert n characters */
		edit.Buffer = strInsertText(edit.Buffer, u.Where, u.Text)
	}

	edit.Cursor = u.Where + u.InsertLength

	s.UndoPoint--
	s.RedoPoint--
}

func (edit *TextEditor) DoRedo() {
	var s *textUndoState = &edit.Undo
	var u *textUndoRecord
	var r textUndoRecord
	if int(s.RedoPoint) == _TEXTEDIT_UNDOSTATECOUNT {
		return
	}

	/* we need to do two things: apply the redo record, and create an undo record */
	u = &s.UndoRec[s.UndoPoint]

	r = s.UndoRec[s.RedoPoint]

	/* we KNOW there must be room for the undo record, because the redo record
	was derived from an undo record */
	u.DeleteLength = r.InsertLength

	u.InsertLength = r.DeleteLength
	u.Where = r.Where
	u.Text = nil

	if r.DeleteLength != 0 {
		u.Text = make([]rune, r.DeleteLength)
		copy(u.Text, edit.Buffer[r.Where:r.Where+r.DeleteLength])
		edit.Buffer = strDeleteText(edit.Buffer, r.Where, r.DeleteLength)
	}

	if r.InsertLength != 0 {
		/* easy case: need to insert n characters */
		edit.Buffer = strInsertText(edit.Buffer, r.Where, r.Text)
	}

	edit.Cursor = r.Where + r.InsertLength

	s.UndoPoint++
	s.RedoPoint++
}

func (state *TextEditor) makeundoInsert(where int, length int) {
	texteditCreateundo(&state.Undo, where, 0, length)
}

func (state *TextEditor) makeundoDelete(where int, length int) {
	u := texteditCreateundo(&state.Undo, where, length, 0)
	u.Text = make([]rune, length)
	copy(u.Text, state.Buffer[where:where+length])
}

func (state *TextEditor) makeundoReplace(where int, old_length int, new_length int) {
	u := texteditCreateundo(&state.Undo, where, old_length, new_length)
	u.Text = make([]rune, old_length)
	copy(u.Text, state.Buffer[where:where+old_length])
}

func (state *TextEditor) clearState(type_ TextEditType) {
	/* reset the state to default */
	state.Undo.UndoPoint = 0

	state.Undo.RedoPoint = int16(_TEXTEDIT_UNDOSTATECOUNT)
	state.HasPreferredX = false
	state.PreferredX = 0
	//state.CursorAtEndOfLine = 0
	state.Initialized = true
	state.SingleLine = type_ == TextEditSingleLine
	state.InsertMode = false
}

func (edit *TextEditor) SelectAll() {
	edit.SelectStart = 0
	edit.SelectEnd = len(edit.Buffer)
}

func (edit *TextEditor) editDrawText(out *command.Buffer, style *nstyle.Edit, pos image.Point, x_margin int, text []rune, textOffset int, row_height int, f font.Face, background color.RGBA, foreground color.RGBA, is_selected bool, md_style int) (posOut image.Point) {
	if len(text) == 0 {
		return pos
	}
	var line_offset int = 0
	var line_count int = 0
	var txt textWidget
	txt.Background = background
	txt.Text = foreground

	pos_x, pos_y := pos.X, pos.Y
	start := 0

	// d := font.Drawer{Face: f}

	tabsz := edit.charSize * tabSizeInSpaces
	pwsz := edit.charSize

	measureText := func(start, end int) int {
		if edit.PasswordChar != 0 {
			return pwsz * (end - start)
		}
		return edit.charSize * (end - start)
	}

	getText := func(start, end int) string {
		if edit.PasswordChar != 0 {
			n := end - start
			if n >= len(edit.password) {
				edit.password = make([]rune, n)
				for i := range edit.password {
					edit.password[i] = edit.PasswordChar
				}
			}
			return string(edit.password[:n])
		}
		return string(text[start:end])
	}

	flushLine := func(index int) rect.Rect {
		// new line sepeator so draw previous line
		var lblrect rect.Rect
		lblrect.Y = pos_y + line_offset
		lblrect.H = row_height
		lblrect.W = nk_null_rect.W
		lblrect.X = pos_x

		if is_selected { // selection needs to draw different background color
			if index == len(text) || (index == start && start == 0) {
				lblrect.W = measureText(start, index)
			}
			out.FillRect(lblrect, 0, background)
		}

		if md_style == mdstyle.MdStyleZkLinkText || md_style == mdstyle.MdStyleSearchSpec {
			sp := image.Point{pos_x, pos_y + row_height - 1}
			ep := image.Point{pos_x + edit.charSize*(index-start), pos_y + row_height - 1}
			out.StrokeLine(sp, ep, 1, edit.Theme.MarkDownTheme[md_style].Color)
		}

		edit.drawchunks = append(edit.drawchunks, drawchunk{lblrect, start + textOffset, index + textOffset})
		widgetText(out, lblrect, getText(start, index), &txt, "LC", f)

		pos_x = x_margin

		return lblrect
	}

	flushTab := func(index int) rect.Rect {
		var lblrect rect.Rect
		lblrect.Y = pos_y + line_offset
		lblrect.H = row_height
		lblrect.W = measureText(start, index)
		lblrect.X = pos_x

		lblrect.W = int(math.Floor(float64(lblrect.X+lblrect.W-x_margin)/float64(tabsz))+1)*tabsz + x_margin - lblrect.X

		if is_selected {
			out.FillRect(lblrect, 0, background)
		}
		edit.drawchunks = append(edit.drawchunks, drawchunk{lblrect, start + textOffset, index + textOffset})
		widgetText(out, lblrect, getText(start, index), &txt, "LC", f)

		pos_x += lblrect.W

		return lblrect
	}

	if start+textOffset == 0 {
		realLineLen := 0
		var llr rune
		for realLineLen, llr = range edit.Buffer[start+textOffset:] {
			if llr == '\r' || llr == '\n' {
				break
			}
		}

		// Highlight cursor line
		if edit.Cursor >= start+textOffset && edit.Cursor <= start+textOffset+realLineLen {
			var lblrect rect.Rect
			lblrect.Y = pos_y + line_offset
			lblrect.H = row_height
			lblrect.W = nk_null_rect.W
			lblrect.X = x_margin
			out.FillRect(lblrect, 0, edit.Theme.LineHighlight)
		}

	}
	for index, glyph := range text {
		switch glyph {
		case '\t':
			if true || index+textOffset >= edit.firstVisibleIndex && index+textOffset <= edit.lastVisibleIndex {
				flushTab(index)
			}
			start = index + 1
		case '\n', '\r':
			if true || index+textOffset >= edit.firstVisibleIndex && index+textOffset <= edit.lastVisibleIndex {
				flushLine(index)
			}
			line_count++
			start = index + 1
			line_offset += row_height

			realLineLen := 0
			var llr rune
			for realLineLen, llr = range edit.Buffer[start+textOffset:] {
				if llr == '\r' || llr == '\n' {
					break
				}
			}

			// Show Images
			if edit.ShowImages {
				var imgbox rect.Rect
				var imgbounds image.Rectangle
				for _, img := range edit.imagesInCurrentLine {
					imgbounds = img.Bounds()
					imgbox.Y = pos_y + line_offset
					imgbox.H = imgbounds.Max.Y - imgbounds.Min.Y
					imgbox.W = imgbounds.Max.X - imgbounds.Min.X
					imgbox.X = x_margin
					// draw images
					out.DrawImage(imgbox, img)
					pos_y += line_offset + imgbox.H
				}
				edit.imagesInCurrentLine = []*image.RGBA{}
			}

			// highlight cursor line
			if edit.Cursor >= start+textOffset && edit.Cursor <= start+textOffset+realLineLen {
				var lblrect rect.Rect
				lblrect.Y = pos_y + line_offset
				lblrect.H = row_height
				lblrect.W = nk_null_rect.W
				lblrect.X = x_margin
				out.FillRect(lblrect, 0, edit.Theme.LineHighlight)
			}
		}
	}

	if start >= len(text) {
		return image.Point{pos_x, pos_y + line_offset}
	}

	// draw last line
	lblrect := flushLine(len(text))
	lblrect.W = measureText(start, len(text))

	// push back any images in the current text
	if md_style == mdstyle.MdStyleImageUrl {
		strimg := string(text)
		img, ok := edit.imageCache[strimg]
		if ok {
			edit.imagesInCurrentLine = append(edit.imagesInCurrentLine, img)
		}
	}

	return image.Point{lblrect.X + lblrect.W, lblrect.Y}
}

func (ed *TextEditor) doEdit(bounds rect.Rect, style *nstyle.Edit, inp *Input, cut, copy, paste bool) (ret EditEvents) {
	font := ed.Font
	state := ed.win.widgets.PrevState(bounds)

	ed.clamp()

	// visible text area calculation
	var area rect.Rect
	area.X = bounds.X + style.Padding.X + style.Border
	area.Y = bounds.Y + style.Padding.Y + style.Border
	area.W = bounds.W - (2.0*style.Padding.X + 2*style.Border)
	area.H = bounds.H - (2.0*style.Padding.Y + 2*style.Border)
	if ed.Flags&EditMultiline != 0 {
		area.H = area.H - style.ScrollbarSize.Y
	}
	var row_height int
	if ed.Flags&EditMultiline != 0 {
		row_height = FontHeight(font) + ed.Theme.LinePadding
	} else {
		row_height = area.H
	}

	/* update edit state */
	prev_state := ed.Active

	if ed.win.ctx.activateEditor != nil {
		if ed.win.ctx.activateEditor == ed {
			ed.Active = true
			if ed.win.flags&windowDocked != 0 {
				ed.win.ctx.dockedWindowFocus = ed.win.idx
			}
		} else {
			ed.Active = false
		}
	}

	is_hovered := inp.Mouse.HoveringRect(bounds)

	if ed.Flags&EditFocusFollowsMouse != 0 {
		if inp != nil {
			ed.Active = is_hovered
		}
	} else {
		if inp != nil && inp.Mouse.Buttons[mouse.ButtonLeft].Clicked && inp.Mouse.Buttons[mouse.ButtonLeft].Down {
			ed.Active = inp.Mouse.HoveringRect(bounds)
		}
	}

	/* (de)activate text editor */
	var select_all bool
	if !prev_state && ed.Active {
		type_ := TextEditSingleLine
		if ed.Flags&EditMultiline != 0 {
			type_ = TextEditMultiLine
		}
		ed.clearState(type_)
		if ed.Flags&EditAlwaysInsertMode != 0 {
			ed.InsertMode = true
		}
		if ed.Flags&EditAutoSelect != 0 {
			select_all = true
		}
	} else if !ed.Active {
		ed.InsertMode = false
	}

	if ed.Flags&EditNeverInsertMode != 0 {
		ed.InsertMode = false
	}

	if ed.Active {
		ret = EditActive
	} else {
		ret = EditInactive
	}
	if prev_state != ed.Active {
		if ed.Active {
			ret |= EditActivated
		} else {
			ret |= EditDeactivated
		}
	}

	/* handle user input */
	cursor_follow := ed.CursorFollow
	ed.CursorFollow = false
	do_reflow := false
	onlystyle := true
	if ed.Active && inp != nil {
		inpos := inp.Mouse.Pos
		indelta := inp.Mouse.Delta
		if indelta.X > 0 || indelta.Y > 0 {
			do_reflow = true
		}

		coord := image.Point{(inpos.X - area.X), (inpos.Y - area.Y)}
		areaWithoutScrollbar := area
		areaWithoutScrollbar.W -= style.ScrollbarSize.X
		is_hovered := inp.Mouse.HoveringRect(areaWithoutScrollbar)

		/* mouse click handler */
		if select_all {
			ed.SelectAll()
			do_reflow = true
		} else if is_hovered && inp.Mouse.Buttons[mouse.ButtonLeft].Down && inp.Mouse.Buttons[mouse.ButtonLeft].Clicked {
			if ed.doubleClick(coord) {
				ed.clickCount++
				if ed.clickCount > 3 {
					ed.clickCount = 3
				}
				do_reflow = true
			} else {
				do_reflow = true
				ed.clickCount = 1
			}
			ed.click(coord, font, row_height)
		} else if is_hovered && inp.Mouse.Buttons[mouse.ButtonLeft].Down && (indelta.X != 0.0 || indelta.Y != 0.0) {
			do_reflow = true
			ed.drag(coord, font, row_height)
			cursor_follow = true
		}

		/* text input */
		if inp.Keyboard.Text != "" {
			// log.Printf("inp.Keyboard.Text = '%s'\n", string(inp.Keyboard.Text))
			ed.Text([]rune(inp.Keyboard.Text))
			cursor_follow = true
			do_reflow = true
			onlystyle = false
		}

		clipboardModifier := key.ModControl
		if runtime.GOOS == "darwin" {
			clipboardModifier = key.ModMeta
		}

		for _, e := range inp.Keyboard.Keys {
			switch e.Code {
			case key.CodeReturnEnter:
				if ed.Flags&EditCtrlEnterNewline != 0 && e.Modifiers&key.ModShift != 0 {
					ed.Text([]rune{'\n'})
					cursor_follow = true
					onlystyle = false
				} else if ed.Flags&EditSigEnter != 0 {
					ret = EditInactive
					ret |= EditDeactivated
					if ed.Flags&EditReadOnly == 0 {
						ret |= EditCommitted
					}
					ed.Active = false
				}

			case key.CodeX:
				if e.Modifiers&clipboardModifier != 0 {
					cut = true
				}

			case key.CodeC:
				if e.Modifiers&clipboardModifier != 0 {
					copy = true
				}

			case key.CodeV:
				if e.Modifiers&clipboardModifier != 0 {
					paste = true
				}

			case key.CodeF:
				if e.Modifiers&clipboardModifier != 0 {
					ed.popupFind()
				}

			case key.CodeG:
				if e.Modifiers&clipboardModifier != 0 {
					ed.lookForward(true)
					cursor_follow = true
				}

			default:
				ed.key(e, font, row_height)
				cursor_follow = true
				onlystyle = false
			}
			do_reflow = true

		}

		/* cut & copy handler */
		if (copy || cut) && (ed.Flags&EditClipboard != 0) {
			var begin, end int
			if ed.SelectStart > ed.SelectEnd {
				begin = ed.SelectEnd
				end = ed.SelectStart
			} else {
				begin = ed.SelectStart
				end = ed.SelectEnd
			}
			clipboard.Set(string(ed.Buffer[begin:end]))
			if cut {
				onlystyle = false
				ed.Cut()
				cursor_follow = true
				do_reflow = true
			}
		}

		/* paste handler */
		if paste && (ed.Flags&EditClipboard != 0) {
			ed.Paste(clipboard.Get())
			cursor_follow = true
			do_reflow = true
			onlystyle = false
		}

		if do_reflow {
			ed.Reflow(onlystyle)
		}
	}

	/* set widget state */
	if ed.Active {
		state = nstyle.WidgetStateActive
	} else {
		state = nstyle.WidgetStateInactive
	}
	if is_hovered {
		state |= nstyle.WidgetStateHovered
	}

	var d drawableTextEditor

	/* text pointer positions */
	var selection_begin, selection_end int
	if ed.SelectStart < ed.SelectEnd {
		selection_begin = ed.SelectStart
		selection_end = ed.SelectEnd
	} else {
		selection_begin = ed.SelectEnd
		selection_end = ed.SelectStart
	}

	d.SelectionBegin, d.SelectionEnd = selection_begin, selection_end

	d.Edit = ed
	d.State = state
	d.Style = style
	d.Scaling = ed.win.ctx.Style.Scaling
	d.Bounds = bounds
	d.Area = area
	ed.textArea = area
	d.RowHeight = row_height
	d.hasInput = inp.Mouse.valid
	ed.win.widgets.Add(state, bounds)

	d.Draw(&ed.win.ctx.Style, &ed.win.cmds)

	/* scrollbar */
	if cursor_follow {
		cursor_pos := d.CursorPos
		/* update scrollbar to follow cursor */
		if ed.Flags&EditNoHorizontalScroll == 0 {
			/* horizontal scroll */
			scroll_increment := area.W / 2
			if (cursor_pos.X < ed.Scrollbar.X) || ((ed.Scrollbar.X+area.W)-cursor_pos.X < FontWidth(font, "i")) {
				ed.Scrollbar.X = max(0, cursor_pos.X-scroll_increment)
			}
		} else {
			ed.Scrollbar.X = 0
		}

		if ed.Flags&EditMultiline != 0 {
			/* vertical scroll */
			if cursor_pos.Y < ed.Scrollbar.Y {
				ed.Scrollbar.Y = max(0, cursor_pos.Y-row_height)
			}
			for (ed.Scrollbar.Y+area.H)-cursor_pos.Y < row_height {
				ed.Scrollbar.Y = ed.Scrollbar.Y + row_height
			}
		} else {
			ed.Scrollbar.Y = 0
		}
	}

	if !ed.SingleLine {
		/* scrollbar widget */
		var scroll rect.Rect
		scroll.X = (area.X + area.W) - style.ScrollbarSize.X
		scroll.Y = area.Y
		scroll.W = style.ScrollbarSize.X
		scroll.H = area.H

		scroll_offset := float64(ed.Scrollbar.Y)
		scroll_step := float64(scroll.H) * 0.1
		if runtime.GOOS == "darwin" {
			scroll_step = float64(scroll.H) * 0.005 // was 0.1
		}
		scroll_inc := float64(scroll.H) * 0.01
		scroll_target := float64(d.TextSize.Y + row_height)
		ed.Scrollbar.Y = int(doScrollbarv(ed.win, scroll, bounds, scroll_offset, scroll_target, scroll_step, scroll_inc, &style.Scrollbar, inp, font))
	}

	return ret
}

type drawableTextEditor struct {
	Edit      *TextEditor
	State     nstyle.WidgetStates
	Style     *nstyle.Edit
	Scaling   float64
	Bounds    rect.Rect
	Area      rect.Rect
	RowHeight int
	hasInput  bool

	SelectionBegin, SelectionEnd int

	TextSize  image.Point
	CursorPos image.Point
}

func (d *drawableTextEditor) Draw(z *nstyle.Style, out *command.Buffer) {
	edit := d.Edit
	state := d.State
	style := d.Style
	bounds := d.Bounds
	font := edit.Font
	area := d.Area
	row_height := d.RowHeight

	selection_begin := d.SelectionBegin
	selection_end := d.SelectionEnd
	if edit.drawchunks != nil {
		edit.drawchunks = edit.drawchunks[:0]
	}

	/* select background colors/images  */
	var old_clip rect.Rect = out.Clip
	{
		var background *nstyle.Item
		if state&nstyle.WidgetStateActive != 0 {
			background = &style.Active
		} else if state&nstyle.WidgetStateHovered != 0 {
			background = &style.Hover
		} else {
			background = &style.Normal
		}

		/* draw background frame */
		if background.Type == nstyle.ItemColor {
			out.FillRect(bounds, style.Rounding, style.BorderColor)
			if edit.Theme != nil {
				out.FillRect(shrinkRect(bounds, style.Border), style.Rounding, edit.Theme.BgColor /*background.Data.Color*/)
			} else {
				out.FillRect(shrinkRect(bounds, style.Border), style.Rounding, background.Data.Color)
			}
		} else {
			out.DrawImage(bounds, background.Data.Image)
		}
	}

	area.W -= FontWidth(font, "i")
	clip := unify(old_clip, area)
	out.PushScissor(clip)
	/* draw text */
	var background_color color.RGBA
	var text_color color.RGBA
	var sel_background_color color.RGBA
	var sel_text_color color.RGBA
	var cursor_color color.RGBA
	var cursor_text_color color.RGBA
	var background *nstyle.Item

	/* select correct colors to draw */
	if state&nstyle.WidgetStateActive != 0 {
		background = &style.Active
		text_color = style.TextActive
		sel_text_color = style.SelectedTextHover
		sel_background_color = style.SelectedHover
		cursor_color = style.CursorHover
		cursor_text_color = style.CursorTextHover
	} else if state&nstyle.WidgetStateHovered != 0 {
		background = &style.Hover
		text_color = style.TextHover
		sel_text_color = style.SelectedTextHover
		sel_background_color = style.SelectedHover
		cursor_text_color = style.CursorTextHover
		cursor_color = style.CursorHover
	} else {
		background = &style.Normal
		text_color = style.TextNormal
		sel_text_color = style.SelectedTextNormal
		sel_background_color = style.SelectedNormal
		cursor_color = style.CursorNormal
		cursor_text_color = style.CursorTextNormal
	}

	if background.Type == nstyle.ItemImage {
		background_color = color.RGBA{0, 0, 0, 0}
	} else {
		background_color = background.Data.Color
	}

	// override with theme
	if edit.Theme != nil {
		cursor_color = edit.Theme.CursorColor
		background_color = edit.Theme.BgColor
		sel_background_color = edit.Theme.SelectionColor
	}

	startPos := image.Point{area.X - edit.Scrollbar.X, area.Y - edit.Scrollbar.Y}
	pos := startPos
	x_margin := pos.X

	edit.firstVisibleLine = edit.Scrollbar.Y / row_height
	edit.lastVisibleLine = edit.firstVisibleLine + edit.textArea.H/row_height

	//	fmt.Printf("Rendering lines %d - %d\n", edit.firstVisibleLine, edit.lastVisibleLine)

	// work out first and last visible index
	line_counter := 0
	edit.firstVisibleIndex = 0
	edit.lastVisibleIndex = len(edit.Buffer)
	for index, r := range edit.Buffer {
		switch r {
		case '\n', '\r':
			line_counter++
			if line_counter == edit.firstVisibleLine {
				edit.firstVisibleIndex = index + 1
			}
			if line_counter == edit.lastVisibleLine+1 {
				edit.lastVisibleIndex = index - 1
			}
		}
	}
	//fmt.Printf("vis ind first: %d, last: %d", edit.firstVisibleIndex, edit.lastVisibleIndex)

	if edit.SelectStart == edit.SelectEnd {
		drawEolCursor := func() {
			cursor_pos := d.CursorPos
			/* draw cursor at end of line */
			var cursor rect.Rect
			if edit.Flags&EditIbeamCursor != 0 {
				cursor.W = int(d.Scaling)
				if cursor.W <= 0 {
					cursor.W = 1
				}
			} else {
				cursor.W = FontWidth(font, "i")
			}
			cursor.H = row_height
			cursor.X = area.X + cursor_pos.X - edit.Scrollbar.X
			cursor.Y = area.Y + cursor_pos.Y + row_height/2.0 - cursor.H/2.0
			cursor.Y -= edit.Scrollbar.Y
			out.FillRect(cursor, 0, cursor_color)
		}

		/* no selection so just draw the complete text */
		if edit.Styler != nil {
			pos = edit.editDrawTextStyled(out, style, pos, x_margin, edit.Buffer[:edit.Cursor], 0, row_height, font, background_color, text_color, false)
		} else {
			pos = edit.editDrawTextUnstyled(out, style, pos, x_margin, edit.Buffer[:edit.Cursor], 0, row_height, font, background_color, text_color, false)
		}
		d.CursorPos = pos.Sub(startPos)
		if edit.Active && d.hasInput {
			if edit.Cursor < len(edit.Buffer) {
				cursorChar := edit.Buffer[edit.Cursor]
				// RENE WRAP
				if cursorChar == '\n' || cursorChar == '\r' || cursorChar == '\t' || edit.Flags&EditIbeamCursor != 0 {
					if edit.Styler != nil {
						pos = edit.editDrawTextStyled(out, style, pos, x_margin, edit.Buffer[edit.Cursor:edit.Cursor+1], edit.Cursor, row_height, font, background_color, text_color, true)
					} else {
						pos = edit.editDrawTextUnstyled(out, style, pos, x_margin, edit.Buffer[edit.Cursor:edit.Cursor+1], edit.Cursor, row_height, font, background_color, text_color, true)
					}
					drawEolCursor()
				} else {
					if edit.Styler != nil {
						pos = edit.editDrawTextStyled(out, style, pos, x_margin, edit.Buffer[edit.Cursor:edit.Cursor+1], edit.Cursor, row_height, font, cursor_color, cursor_text_color, true)
					} else {
						pos = edit.editDrawTextUnstyled(out, style, pos, x_margin, edit.Buffer[edit.Cursor:edit.Cursor+1], edit.Cursor, row_height, font, cursor_color, cursor_text_color, true)
					}
				}
				if edit.Styler != nil {
					pos = edit.editDrawTextStyled(out, style, pos, x_margin, edit.Buffer[edit.Cursor+1:], edit.Cursor+1, row_height, font, background_color, text_color, false)
				} else {
					pos = edit.editDrawTextUnstyled(out, style, pos, x_margin, edit.Buffer[edit.Cursor+1:], edit.Cursor+1, row_height, font, background_color, text_color, false)
				}
			} else {
				drawEolCursor()
			}
		} else if edit.Cursor < len(edit.Buffer) {
			if edit.Styler != nil {
				pos = edit.editDrawTextStyled(out, style, pos, x_margin, edit.Buffer[edit.Cursor:], edit.Cursor, row_height, font, background_color, text_color, false)
			} else {
				pos = edit.editDrawTextUnstyled(out, style, pos, x_margin, edit.Buffer[edit.Cursor:], edit.Cursor, row_height, font, background_color, text_color, false)
			}
		}
	} else {
		/* edit has selection so draw 1-3 text chunks */
		if selection_begin > 0 {
			/* draw unselected text before selection */
			if edit.Styler != nil {
				pos = edit.editDrawTextStyled(out, style, pos, x_margin, edit.Buffer[:selection_begin], 0, row_height, font, background_color, text_color, false)
			} else {
				pos = edit.editDrawTextUnstyled(out, style, pos, x_margin, edit.Buffer[:selection_begin], 0, row_height, font, background_color, text_color, false)
			}
		}

		if selection_begin == edit.SelectEnd {
			d.CursorPos = pos.Sub(startPos)
		}

		if edit.Styler != nil {
			pos = edit.editDrawTextStyled(out, style, pos, x_margin, edit.Buffer[selection_begin:selection_end], selection_begin, row_height, font, sel_background_color, sel_text_color, true)
		} else {
			pos = edit.editDrawTextUnstyled(out, style, pos, x_margin, edit.Buffer[selection_begin:selection_end], selection_begin, row_height, font, sel_background_color, sel_text_color, true)
		}

		if selection_begin != edit.SelectEnd {
			d.CursorPos = pos.Sub(startPos)
		}

		if selection_end < len(edit.Buffer) {
			if edit.Styler != nil {
				pos = edit.editDrawTextStyled(out, style, pos, x_margin, edit.Buffer[selection_end:], selection_end, row_height, font, background_color, text_color, false)
			} else {
				pos = edit.editDrawTextUnstyled(out, style, pos, x_margin, edit.Buffer[selection_end:], selection_end, row_height, font, background_color, text_color, false)
			}
		}
	}
	d.TextSize = pos.Sub(startPos)

	// fix rectangles in drawchunks by subtracting area from them
	for i := range edit.drawchunks {
		edit.drawchunks[i].X -= area.X
		edit.drawchunks[i].Y -= area.Y

		//dc := edit.drawchunks[i]
		//		fmt.Printf("Draw chunk: %d,%d - %d,%d : %s\n", dc.Min().X, dc.Min().Y, dc.Max().X, dc.Max().Y, string(edit.Buffer[dc.start:dc.end]))
	}
	//	fmt.Println()

	out.PushScissor(old_clip)
	return
}

func (edit *TextEditor) editDrawTextStyled(out *command.Buffer, style *nstyle.Edit, pos image.Point, x_margin int, text []rune, textOffset int, row_height int, f font.Face, background color.RGBA, foreground color.RGBA, is_selected bool) (posOut image.Point) {
	if len(text) == 0 {
		return pos
	}

	styleRanges := edit.Styler.StyleRanges()

	currentStyleRange := styleRanges[0]
	currentStyleIndex := 0
	// find initial style range based on textOffset
	for currentStyleIndex, currentStyleRange = range styleRanges {
		if textOffset >= currentStyleRange.Start && textOffset <= currentStyleRange.End {
			break
		}
	}
	//fmt.Printf("currentStyleIndex %d of %d found for textOffset %d of textLen %d \n", currentStyleIndex, len(styleRanges), textOffset, len(text))

	// now start drawing
	textLen := len(text)
	posOut = pos
	currentStyleRange.Start = textOffset
	i := 0
	for {
		chars_in_range := currentStyleRange.End - currentStyleRange.Start + 1
		if chars_in_range+i >= textLen {
			chars_in_range = textLen - i
		}
		textpart := text[i : i+chars_in_range]
		//fmt.Printf("i: %d, textpart: '%s'\n", i, string(textpart))
		fgcolor := edit.Theme.MarkDownTheme[currentStyleRange.Style].Color
		f = edit.Theme.MdFonts[edit.Theme.MarkDownTheme[currentStyleRange.Style].FontStyle]
		posOut = edit.editDrawText(out, style, posOut, x_margin, textpart, textOffset+i, row_height, f, background, fgcolor, is_selected, currentStyleRange.Style)
		i += chars_in_range
		if i < textLen {
			currentStyleIndex++
			currentStyleRange = styleRanges[currentStyleIndex]
		} else {
			break
		}
	}
	return posOut
}

func (edit *TextEditor) editDrawTextUnstyled(out *command.Buffer, style *nstyle.Edit, pos image.Point, x_margin int, text []rune, textOffset int, row_height int, f font.Face, background color.RGBA, foreground color.RGBA, is_selected bool) (posOut image.Point) {
	if len(text) == 0 {
		return pos
	}
	var line_offset int = 0
	var line_count int = 0
	var txt textWidget
	txt.Background = background
	txt.Text = foreground

	pos_x, pos_y := pos.X, pos.Y
	start := 0

	d := font.Drawer{Face: f}

	tabsz := d.MeasureString(" ").Ceil() * tabSizeInSpaces
	pwsz := d.MeasureString("*").Ceil()

	measureText := func(start, end int) int {
		if edit.PasswordChar != 0 {
			return pwsz * (end - start)
		}
		// XXX calculating text width here is slow figure out why
		return d.MeasureString(string(text[start:end])).Ceil()
	}

	getText := func(start, end int) string {
		if edit.PasswordChar != 0 {
			n := end - start
			if n >= len(edit.password) {
				edit.password = make([]rune, n)
				for i := range edit.password {
					edit.password[i] = edit.PasswordChar
				}
			}
			return string(edit.password[:n])
		}
		return string(text[start:end])
	}

	flushLine := func(index int) rect.Rect {
		// new line sepeator so draw previous line
		var lblrect rect.Rect
		lblrect.Y = pos_y + line_offset
		lblrect.H = row_height
		lblrect.W = nk_null_rect.W
		lblrect.X = pos_x

		if is_selected { // selection needs to draw different background color
			if index == len(text) || (index == start && start == 0) {
				lblrect.W = measureText(start, index)
			}
			out.FillRect(lblrect, 0, background)
		}
		edit.drawchunks = append(edit.drawchunks, drawchunk{lblrect, start + textOffset, index + textOffset})
		widgetText(out, lblrect, getText(start, index), &txt, "LC", f)

		pos_x = x_margin

		return lblrect
	}

	flushTab := func(index int) rect.Rect {
		var lblrect rect.Rect
		lblrect.Y = pos_y + line_offset
		lblrect.H = row_height
		lblrect.W = measureText(start, index)
		lblrect.X = pos_x

		lblrect.W = int(math.Floor(float64(lblrect.X+lblrect.W-x_margin)/float64(tabsz))+1)*tabsz + x_margin - lblrect.X

		if is_selected {
			out.FillRect(lblrect, 0, background)
		}
		edit.drawchunks = append(edit.drawchunks, drawchunk{lblrect, start + textOffset, index + textOffset})
		widgetText(out, lblrect, getText(start, index), &txt, "LC", f)

		pos_x += lblrect.W

		return lblrect
	}

	pos_in_line := 0
	for index, glyph := range text {
		pos_in_line++
		switch glyph {
		case '\t':
			flushTab(index)
			start = index + 1
		case '\n':
			flushLine(index)
			line_count++
			start = index + 1
			line_offset += row_height
			pos_in_line = 0
		case '\r':
			flushLine(index)
			line_count++
			start = index + 1
			line_offset += row_height
			pos_in_line = 0

		}
	}

	if start >= len(text) {
		return image.Point{pos_x, pos_y + line_offset}
	}

	// draw last line
	lblrect := flushLine(len(text))
	lblrect.W = measureText(start, len(text))

	return image.Point{lblrect.X + lblrect.W, lblrect.Y}
}

func runeSliceEquals(a, b []rune) bool {
	if len(a) != len(b) {
		return false
	}
	for i := range a {
		if a[i] != b[i] {
			return false
		}
	}
	return true
}

var clipboardModifier = func() key.Modifiers {
	if runtime.GOOS == "darwin" {
		return key.ModMeta
	}
	return key.ModControl
}()

func (edit *TextEditor) popupFind() {
	if edit.Flags&EditMultiline == 0 {
		return
	}
	var searchEd TextEditor
	searchEd.Flags = EditSigEnter | EditClipboard | EditSelectable
	searchEd.Buffer = append(searchEd.Buffer[:0], edit.needle...)
	searchEd.SelectStart = 0
	searchEd.SelectEnd = len(searchEd.Buffer)
	searchEd.Cursor = searchEd.SelectEnd
	searchEd.Active = true

	edit.SelectEnd = edit.SelectStart
	edit.Cursor = edit.SelectStart

	edit.win.Master().PopupOpen("Search...", WindowTitle|WindowNoScrollbar|WindowMovable|WindowBorder|WindowDynamic, rect.Rect{100, 100, 400, 500}, true, func(w *Window) {
		w.Row(30).Static()
		w.LayoutFitWidth(0, 30)
		w.Label("Search: ", "LC")
		w.LayoutSetWidth(150)
		ev := searchEd.Edit(w)
		if ev&EditCommitted != 0 {
			edit.Active = true
			w.Close()
		}
		w.LayoutSetWidth(100)
		if w.ButtonText("Done") {
			edit.Active = true
			w.Close()
		}
		kbd := &w.Input().Keyboard
		for _, k := range kbd.Keys {
			switch {
			case k.Modifiers == clipboardModifier && k.Code == key.CodeG:
				edit.lookForward(true)
			case k.Modifiers == 0 && k.Code == key.CodeEscape:
				edit.SelectEnd = edit.SelectStart
				edit.Cursor = edit.SelectStart
				edit.Active = true
				w.Close()
			}
		}
		if !runeSliceEquals(searchEd.Buffer, edit.needle) {
			edit.needle = append(edit.needle[:0], searchEd.Buffer...)
			edit.lookForward(false)
		}
	})
}

func (edit *TextEditor) lookForward(forceAdvance bool) {
	if edit.Flags&EditMultiline == 0 {
		return
	}
	if edit.hasSelection() {
		if forceAdvance {
			edit.SelectStart = edit.SelectEnd
		} else {
			edit.SelectEnd = edit.SelectStart
		}
		if edit.SelectEnd >= 0 {
			edit.Cursor = edit.SelectEnd
		}
	}
	for start := edit.Cursor; start < len(edit.Buffer); start++ {
		found := true
		for i := 0; i < len(edit.needle); i++ {
			if edit.needle[i] != edit.Buffer[start+i] {
				found = false
				break
			}
		}
		if found {
			edit.SelectStart = start
			edit.SelectEnd = start + len(edit.needle)
			edit.Cursor = edit.SelectEnd
			edit.CursorFollow = true
			return
		}
	}
	edit.SelectStart = 0
	edit.SelectEnd = 0
	edit.Cursor = 0
	edit.CursorFollow = true
}

// Adds text editor edit to win.
// Initial contents of the text editor will be set to text. If
// alwaysSet is specified the contents of the editor will be reset
// to text.
func (edit *TextEditor) Edit(win *Window) EditEvents {
	edit.init(win)
	if edit.Maxlen > 0 {
		if len(edit.Buffer) > edit.Maxlen {
			edit.Buffer = edit.Buffer[:edit.Maxlen]
		}
	}

	if edit.Flags&EditNoCursor != 0 {
		edit.Cursor = len(edit.Buffer)
	}
	if edit.Flags&EditSelectable == 0 {
		edit.SelectStart = edit.Cursor
		edit.SelectEnd = edit.Cursor
	}

	var bounds rect.Rect

	style := &edit.win.ctx.Style
	widget_state, bounds, _ := edit.win.widget()
	if !widget_state {
		return 0
	}
	in := edit.win.inputMaybe(widget_state)

	var cut, copy, paste bool

	if w := win.ContextualOpen(0, image.Point{}, bounds, nil); w != nil {
		w.Row(20).Dynamic(1)
		visible := false
		if edit.Flags&EditClipboard != 0 {
			visible = true
			if w.MenuItem(label.TA("Cut", "LC")) {
				cut = true
			}
			if w.MenuItem(label.TA("Copy", "LC")) {
				copy = true
			}
			if w.MenuItem(label.TA("Paste", "LC")) {
				paste = true
			}
		}
		if edit.Flags&EditMultiline != 0 {
			visible = true
			if w.MenuItem(label.TA("Find...", "LC")) {
				edit.popupFind()
			}
		}
		if !visible {
			w.Close()
		}
	}

	ev := edit.doEdit(bounds, &style.Edit, in, cut, copy, paste)
	return ev
}

// UpdateImageCache updates the image cache -- make sure the buffer is already STYLED!
func (edit *TextEditor) UpdateImageCache() {
	styleRanges := edit.Styler.StyleRanges()
	edit.imageCache = make(map[string]*image.RGBA)

	// find initial style range based on textOffset
	for _, currentStyleRange := range styleRanges {
		if currentStyleRange.Style == mdstyle.MdStyleImageUrl || currentStyleRange.Style == mdstyle.MdStyleLinkUrl {
			filename := strings.ReplaceAll(string(edit.Buffer[currentStyleRange.Start:currentStyleRange.End+1]), "\r", "")
			img, err := LoadImage(filename)
			fmt.Printf("Image: '%s'", filename)
			if err == nil {
				edit.imageCache[filename] = img
				fmt.Println(" : OK")
			} else {
				fmt.Printf(" : FAIL : %s\n", err)
			}
		}
	}

	fmt.Printf("%v", edit.imageCache)
}
