package text

import (
	"bytes"

	"github.com/yuin/goldmark/util"
)

var space = []byte(" ")

// A Segment struct holds information about source positions.
type Segment struct {
	// Start is a start position of the segment.
	Start int

	// Stop is a stop position of the segment.
	// This value should be excluded.
	Stop int

	// Padding is a padding length of the segment.
	Padding int

	// ForceNewline is true if the segment should be ended with a newline.
	// Some elements(i.e. CodeBlock, FencedCodeBlock) does not trim trailing
	// newlines. Spec defines that EOF is treated as a newline, so we need to
	// add a newline to the end of the segment if it is not empty.
	//
	// i.e.:
	//
	//     ```go
	//     const test = "test"
	//
	// This code does not close the code block and ends with EOF. In this case,
	// we need to add a newline to the end of the last line like `const test = "test"\n`.
	ForceNewline bool
}

// NewSegment return a new Segment.
func NewSegment(start, stop int) Segment {
	return Segment{
		Start:   start,
		Stop:    stop,
		Padding: 0,
	}
}

// NewSegmentPadding returns a new Segment with the given padding.
func NewSegmentPadding(start, stop, n int) Segment {
	return Segment{
		Start:   start,
		Stop:    stop,
		Padding: n,
	}
}

// Value returns a value of the segment.
func (t *Segment) Value(buffer []byte) []byte {
	var result []byte
	if t.Padding == 0 {
		result = buffer[t.Start:t.Stop]
	} else {
		result = make([]byte, 0, t.Padding+t.Stop-t.Start+1)
		result = append(result, bytes.Repeat(space, t.Padding)...)
		result = append(result, buffer[t.Start:t.Stop]...)
	}
	if t.ForceNewline && len(result) > 0 && result[len(result)-1] != '\n' {
		result = append(result, '\n')
	}
	return result
}

// Len returns a length of the segment.
func (t *Segment) Len() int {
	return t.Stop - t.Start + t.Padding
}

// Between returns a segment between this segment and the given segment.
func (t *Segment) Between(other Segment) Segment {
	if t.Stop != other.Stop {
		panic("invalid state")
	}
	return NewSegmentPadding(
		t.Start,
		other.Start,
		t.Padding-other.Padding,
	)
}

// IsEmpty returns true if this segment is empty, otherwise false.
func (t *Segment) IsEmpty() bool {
	return t.Start >= t.Stop && t.Padding == 0
}

// TrimRightSpace returns a new segment by slicing off all trailing
// space characters.
func (t *Segment) TrimRightSpace(buffer []byte) Segment {
	v := buffer[t.Start:t.Stop]
	l := util.TrimRightSpaceLength(v)
	if l == len(v) {
		return NewSegment(t.Start, t.Start)
	}
	return NewSegmentPadding(t.Start, t.Stop-l, t.Padding)
}

// TrimLeftSpace returns a new segment by slicing off all leading
// space characters including padding.
func (t *Segment) TrimLeftSpace(buffer []byte) Segment {
	v := buffer[t.Start:t.Stop]
	l := util.TrimLeftSpaceLength(v)
	return NewSegment(t.Start+l, t.Stop)
}

// TrimLeftSpaceWidth returns a new segment by slicing off leading space
// characters until the given width.
func (t *Segment) TrimLeftSpaceWidth(width int, buffer []byte) Segment {
	padding := t.Padding
	for ; width > 0; width-- {
		if padding == 0 {
			break
		}
		padding--
	}
	if width == 0 {
		return NewSegmentPadding(t.Start, t.Stop, padding)
	}
	text := buffer[t.Start:t.Stop]
	start := t.Start
	for _, c := range text {
		if start >= t.Stop-1 || width <= 0 {
			break
		}
		if c == ' ' {
			width--
		} else if c == '\t' {
			width -= 4
		} else {
			break
		}
		start++
	}
	if width < 0 {
		padding = width * -1
	}
	return NewSegmentPadding(start, t.Stop, padding)
}

// WithStart returns a new Segment with same value except Start.
func (t *Segment) WithStart(v int) Segment {
	return NewSegmentPadding(v, t.Stop, t.Padding)
}

// WithStop returns a new Segment with same value except Stop.
func (t *Segment) WithStop(v int) Segment {
	return NewSegmentPadding(t.Start, v, t.Padding)
}

// ConcatPadding concats the padding to the given slice.
func (t *Segment) ConcatPadding(v []byte) []byte {
	if t.Padding > 0 {
		return append(v, bytes.Repeat(space, t.Padding)...)
	}
	return v
}

// Segments is a collection of the Segment.
type Segments struct {
	values []Segment
}

// NewSegments return a new Segments.
func NewSegments() *Segments {
	return &Segments{
		values: nil,
	}
}

// Append appends the given segment after the tail of the collection.
func (s *Segments) Append(t Segment) {
	s.values = append(s.values, t)
}

// AppendAll appends all elements of given segments after the tail of the collection.
func (s *Segments) AppendAll(t []Segment) {
	s.values = append(s.values, t...)
}

// Len returns the length of the collection.
func (s *Segments) Len() int {
	if s.values == nil {
		return 0
	}
	return len(s.values)
}

// At returns a segment at the given index.
func (s *Segments) At(i int) Segment {
	return s.values[i]
}

// Set sets the given Segment.
func (s *Segments) Set(i int, v Segment) {
	s.values[i] = v
}

// SetSliced replace the collection with a subsliced value.
func (s *Segments) SetSliced(lo, hi int) {
	s.values = s.values[lo:hi]
}

// Sliced returns a subslice of the collection.
func (s *Segments) Sliced(lo, hi int) []Segment {
	return s.values[lo:hi]
}

// Clear delete all element of the collection.
func (s *Segments) Clear() {
	s.values = nil
}

// Unshift insert the given Segment to head of the collection.
func (s *Segments) Unshift(v Segment) {
	s.values = append(s.values[0:1], s.values[0:]...)
	s.values[0] = v
}

// Value returns a string value of the collection.
func (s *Segments) Value(buffer []byte) []byte {
	var result []byte
	for _, v := range s.values {
		result = append(result, v.Value(buffer)...)
	}
	return result
}
