// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package markdown

import "unicode/utf8"

var blockquoteTerminatedBy []BlockRule

func ruleBlockQuote(s *StateBlock, startLine, endLine int, silent bool) bool {
	if s.SCount[startLine]-s.BlkIndent >= 4 {
		return false
	}

	pos := s.BMarks[startLine] + s.TShift[startLine]
	max := s.EMarks[startLine]
	src := s.Src

	if pos >= max {
		return false
	}
	if src[pos] != '>' {
		return false
	}
	pos++

	if silent {
		return true
	}

	initial := s.SCount[startLine] + pos - (s.BMarks[startLine] + s.TShift[startLine])
	offset := initial

	spaceAfterMarker := false
	adjustTab := false
	if pos < max {
		if src[pos] == ' ' {
			pos++
			initial++
			offset++
			spaceAfterMarker = true
		} else if src[pos] == '\t' {
			spaceAfterMarker = true
			if (s.BSCount[startLine]+offset)%4 == 3 {
				pos++
				initial++
				offset++
			} else {
				adjustTab = true
			}
		}
	}

	oldBMarks := []int{s.BMarks[startLine]}
	s.BMarks[startLine] = pos

	for pos < max {
		r, size := utf8.DecodeRuneInString(src[pos:])
		if runeIsSpace(r) {
			if r == '\t' {
				d := 0
				if adjustTab {
					d = 1
				}
				offset += 4 - (offset+s.BSCount[startLine]+d)%4
			} else {
				offset++
			}
		} else {
			break
		}
		pos += size
	}

	oldBSCount := []int{s.BSCount[startLine]}
	d := 0
	if spaceAfterMarker {
		d = 1
	}
	s.BSCount[startLine] = s.SCount[startLine] + 1 + d

	lastLineEmpty := pos >= max

	oldSCount := []int{s.SCount[startLine]}
	s.SCount[startLine] = offset - initial

	oldTShift := []int{s.TShift[startLine]}
	s.TShift[startLine] = pos - s.BMarks[startLine]

	oldParentType := s.ParentType
	s.ParentType = ptBlockQuote
	wasOutdented := false

	oldLineMax := s.LineMax

	nextLine := startLine + 1
	for ; nextLine < endLine; nextLine++ {
		if s.SCount[nextLine] < s.BlkIndent {
			wasOutdented = true
		}
		pos = s.BMarks[nextLine] + s.TShift[nextLine]
		max = s.EMarks[nextLine]

		if pos >= max {
			break
		}

		pos++
		if src[pos-1] == '>' && !wasOutdented {
			initial = s.SCount[nextLine] + pos + (s.BMarks[nextLine] + s.TShift[nextLine])
			offset = initial

			if pos >= len(src) || src[pos] != ' ' && src[pos] != '\t' {
				spaceAfterMarker = true
			} else if src[pos] == ' ' {
				pos++
				initial++
				offset++
				adjustTab = false
				spaceAfterMarker = true
			} else if src[pos] == '\t' {
				spaceAfterMarker = true

				if (s.BSCount[nextLine]+offset)%4 == 3 {
					pos++
					initial++
					offset++
					adjustTab = false
				} else {
					adjustTab = true
				}
			}

			oldBMarks = append(oldBMarks, s.BMarks[nextLine])
			s.BMarks[nextLine] = pos

			for pos < max {
				r, size := utf8.DecodeRuneInString(src[pos:])
				if runeIsSpace(r) {
					if r == '\t' {
						d := 0
						if adjustTab {
							d = 1
						}
						offset += 4 - (offset+s.BSCount[startLine]+d)%4
					} else {
						offset++
					}
				} else {
					break
				}
				pos += size
			}

			lastLineEmpty = pos >= max

			oldBSCount = append(oldBSCount, s.BSCount[nextLine])
			d := 0
			if spaceAfterMarker {
				d = 1
			}
			s.BSCount[nextLine] = s.SCount[nextLine] + 1 + d

			oldSCount = append(oldSCount, s.SCount[nextLine])
			s.SCount[nextLine] = offset - initial

			oldTShift = append(oldTShift, s.TShift[nextLine])
			s.TShift[nextLine] = pos - s.BMarks[nextLine]

			continue
		}

		if lastLineEmpty {
			break
		}

		terminate := false
		for _, r := range blockquoteTerminatedBy {
			if r(s, nextLine, endLine, true) {
				terminate = true
				break
			}
		}

		if terminate {
			s.LineMax = nextLine

			if s.BlkIndent != 0 {
				oldBMarks = append(oldBMarks, s.BMarks[nextLine])
				oldBSCount = append(oldBSCount, s.BSCount[nextLine])
				oldTShift = append(oldTShift, s.TShift[nextLine])
				oldSCount = append(oldSCount, s.SCount[nextLine])
				s.SCount[nextLine] -= s.BlkIndent
			}

			break
		}

		oldBMarks = append(oldBMarks, s.BMarks[nextLine])
		oldBSCount = append(oldBSCount, s.BSCount[nextLine])
		oldTShift = append(oldTShift, s.TShift[nextLine])
		oldSCount = append(oldSCount, s.SCount[nextLine])

		s.SCount[nextLine] = -1
	}

	oldIndent := s.BlkIndent
	s.BlkIndent = 0

	tok := &BlockquoteOpen{
		Map: [2]int{startLine, 0},
	}
	s.PushOpeningToken(tok)

	s.Md.Block.Tokenize(s, startLine, nextLine)

	s.PushClosingToken(&BlockquoteClose{})

	s.LineMax = oldLineMax
	s.ParentType = oldParentType
	tok.Map[1] = s.Line

	for i := 0; i < len(oldTShift); i++ {
		s.BMarks[startLine+i] = oldBMarks[i]
		s.TShift[startLine+i] = oldTShift[i]
		s.SCount[startLine+i] = oldSCount[i]
		s.BSCount[startLine+i] = oldBSCount[i]
	}
	s.BlkIndent = oldIndent

	return true
}