// 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 "strconv"

var listTerminatedBy []BlockRule

func skipBulletListMarker(s *StateBlock, startLine int) int {
	pos := s.BMarks[startLine] + s.TShift[startLine]
	max := s.EMarks[startLine]
	src := s.Src

	if pos >= max {
		return -1
	}

	marker := src[pos]
	if marker != '*' && marker != '-' && marker != '+' {
		return -1
	}
	pos++

	if pos < max && !byteIsSpace(src[pos]) {
		return -1
	}

	return pos
}

func skipOrderedListMarker(s *StateBlock, startLine int) int {
	start := s.BMarks[startLine] + s.TShift[startLine]
	pos := start
	max := s.EMarks[startLine]

	if pos+1 >= max {
		return -1
	}

	src := s.Src
	ch := src[pos]
	if ch < '0' || ch > '9' {
		return -1
	}
	pos++

	for {
		if pos >= max {
			return -1
		}

		ch = src[pos]
		pos++

		if ch >= '0' && ch <= '9' {
			if pos-start >= 10 {
				return -1
			}
			continue
		}

		if ch == ')' || ch == '.' {
			break
		}

		return -1
	}

	if pos < max && !byteIsSpace(src[pos]) {
		return -1
	}

	return pos
}

func markParagraphsTight(s *StateBlock, idx int) {
	level := s.Level + 2
	tokens := s.Tokens

	for i := idx + 2; i < len(tokens)-2; i++ {
		if tokens[i].Level() == level {
			if tok, ok := tokens[i].(*ParagraphOpen); ok {
				tok.Hidden = true
				i += 2
				tokens[i].(*ParagraphClose).Hidden = true
			}
		}
	}
}

func ruleList(s *StateBlock, startLine, endLine int, silent bool) bool {
	isTerminatingParagraph := false
	tight := true

	if s.SCount[startLine]-s.BlkIndent >= 4 {
		return false
	}
	src := s.Src

	if silent && s.ParentType == ptParagraph {
		if s.TShift[startLine] >= s.BlkIndent {
			isTerminatingParagraph = true
		}
	}

	var start int
	var markerValue int

	isOrdered := false
	posAfterMarker := skipOrderedListMarker(s, startLine)
	if posAfterMarker > 0 {
		isOrdered = true
		start = s.BMarks[startLine] + s.TShift[startLine]
		markerValue, _ = strconv.Atoi(src[start : posAfterMarker-1])

		if isTerminatingParagraph && markerValue != 1 {
			return false
		}
	} else {
		posAfterMarker = skipBulletListMarker(s, startLine)
		if posAfterMarker < 0 {
			return false
		}
	}

	if isTerminatingParagraph {
		if s.SkipSpaces(posAfterMarker) >= s.EMarks[startLine] {
			return false
		}
	}

	markerChar := src[posAfterMarker-1]

	if silent {
		return true
	}

	tokenIdx := len(s.Tokens)

	var listMap *[2]int
	if isOrdered {
		tok := &OrderedListOpen{
			Order: markerValue,
			Map:   [2]int{startLine, 0},
		}
		s.PushOpeningToken(tok)
		listMap = &tok.Map
	} else {
		tok := &BulletListOpen{
			Map: [2]int{startLine, 0},
		}
		s.PushOpeningToken(tok)
		listMap = &tok.Map
	}

	nextLine := startLine
	prevEmptyEnd := false

	oldParentType := s.ParentType
	s.ParentType = ptList

	var pos int
	var contentStart int

outer:
	for nextLine < endLine {
		pos = posAfterMarker
		max := s.EMarks[nextLine]

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

	loop:
		for pos < max {
			switch src[pos] {
			case '\t':
				offset += 4 - (offset+s.BSCount[nextLine])%4
			case ' ':
				offset++
			default:
				break loop
			}
			pos++
		}

		contentStart = pos

		indentAfterMarker := 1
		if contentStart < max {
			if iam := offset - initial; iam <= 4 {
				indentAfterMarker = iam
			}
		}

		indent := initial + indentAfterMarker

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

		oldIndent := s.BlkIndent
		oldTight := s.Tight
		oldTShift := s.TShift[startLine]
		oldLIndent := s.SCount[startLine]
		s.BlkIndent = indent
		s.Tight = true
		s.TShift[startLine] = contentStart - s.BMarks[startLine]
		s.SCount[startLine] = offset

		if contentStart >= max && s.IsLineEmpty(startLine+1) {
			s.Line = min(s.Line+2, endLine)
		} else {
			s.Md.Block.Tokenize(s, startLine, endLine)
		}

		if !s.Tight || prevEmptyEnd {
			tight = false
		}

		prevEmptyEnd = s.Line-startLine > 1 && s.IsLineEmpty(s.Line-1)

		s.BlkIndent = oldIndent
		s.TShift[startLine] = oldTShift
		s.SCount[startLine] = oldLIndent
		s.Tight = oldTight

		s.PushClosingToken(&ListItemClose{})

		startLine = s.Line
		nextLine = startLine
		(*itemMap)[1] = nextLine

		if nextLine >= endLine {
			break
		}

		contentStart = s.BMarks[startLine]

		if s.SCount[nextLine] < s.BlkIndent {
			break
		}

		for _, r := range listTerminatedBy {
			if r(s, nextLine, endLine, true) {
				break outer
			}
		}

		if isOrdered {
			posAfterMarker = skipOrderedListMarker(s, nextLine)
			if posAfterMarker < 0 {
				break
			}
		} else {
			posAfterMarker = skipBulletListMarker(s, nextLine)
			if posAfterMarker < 0 {
				break
			}
		}

		if markerChar != src[posAfterMarker-1] {
			break
		}
	}

	if isOrdered {
		s.PushClosingToken(&OrderedListClose{})
	} else {
		s.PushClosingToken(&BulletListClose{})
	}

	(*listMap)[1] = nextLine
	s.Line = nextLine
	s.ParentType = oldParentType

	if tight {
		markParagraphsTight(s, tokenIdx)
	}

	return true
}