// Package parser contains stuff that are related to parsing a Markdown text.
package parser

import (
	"fmt"
	"strings"
	"sync"

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

// A Reference interface represents a link reference in Markdown text.
type Reference interface {
	// String implements Stringer.
	String() string

	// Label returns a label of the reference.
	Label() []byte

	// Destination returns a destination(URL) of the reference.
	Destination() []byte

	// Title returns a title of the reference.
	Title() []byte
}

type reference struct {
	label       []byte
	destination []byte
	title       []byte
}

// NewReference returns a new Reference.
func NewReference(label, destination, title []byte) Reference {
	return &reference{label, destination, title}
}

func (r *reference) Label() []byte {
	return r.label
}

func (r *reference) Destination() []byte {
	return r.destination
}

func (r *reference) Title() []byte {
	return r.title
}

func (r *reference) String() string {
	return fmt.Sprintf("Reference{Label:%s, Destination:%s, Title:%s}", r.label, r.destination, r.title)
}

// An IDs interface is a collection of the element ids.
type IDs interface {
	// Generate generates a new element id.
	Generate(value []byte, kind ast.NodeKind) []byte

	// Put puts a given element id to the used ids table.
	Put(value []byte)
}

type ids struct {
	values map[string]bool
}

func newIDs() IDs {
	return &ids{
		values: map[string]bool{},
	}
}

func (s *ids) Generate(value []byte, kind ast.NodeKind) []byte {
	value = util.TrimLeftSpace(value)
	value = util.TrimRightSpace(value)
	result := []byte{}
	for i := 0; i < len(value); {
		v := value[i]
		l := util.UTF8Len(v)
		i += int(l)
		if l != 1 {
			continue
		}
		if util.IsAlphaNumeric(v) {
			if 'A' <= v && v <= 'Z' {
				v += 'a' - 'A'
			}
			result = append(result, v)
		} else if util.IsSpace(v) || v == '-' || v == '_' {
			result = append(result, '-')
		}
	}
	if len(result) == 0 {
		if kind == ast.KindHeading {
			result = []byte("heading")
		} else {
			result = []byte("id")
		}
	}
	if _, ok := s.values[util.BytesToReadOnlyString(result)]; !ok {
		s.values[util.BytesToReadOnlyString(result)] = true
		return result
	}
	for i := 1; ; i++ {
		newResult := fmt.Sprintf("%s-%d", result, i)
		if _, ok := s.values[newResult]; !ok {
			s.values[newResult] = true
			return []byte(newResult)
		}

	}
}

func (s *ids) Put(value []byte) {
	s.values[util.BytesToReadOnlyString(value)] = true
}

// ContextKey is a key that is used to set arbitrary values to the context.
type ContextKey int

// ContextKeyMax is a maximum value of the ContextKey.
var ContextKeyMax ContextKey

// NewContextKey return a new ContextKey value.
func NewContextKey() ContextKey {
	ContextKeyMax++
	return ContextKeyMax
}

// A Context interface holds a information that are necessary to parse
// Markdown text.
type Context interface {
	// String implements Stringer.
	String() string

	// Get returns a value associated with the given key.
	Get(ContextKey) interface{}

	// Set sets the given value to the context.
	Set(ContextKey, interface{})

	// AddReference adds the given reference to this context.
	AddReference(Reference)

	// Reference returns (a reference, true) if a reference associated with
	// the given label exists, otherwise (nil, false).
	Reference(label string) (Reference, bool)

	// References returns a list of references.
	References() []Reference

	// IDs returns a collection of the element ids.
	IDs() IDs

	// BlockOffset returns a first non-space character position on current line.
	// This value is valid only for BlockParser.Open.
	// BlockOffset returns -1 if current line is blank.
	BlockOffset() int

	// BlockOffset sets a first non-space character position on current line.
	// This value is valid only for BlockParser.Open.
	SetBlockOffset(int)

	// BlockIndent returns an indent width on current line.
	// This value is valid only for BlockParser.Open.
	// BlockIndent returns -1 if current line is blank.
	BlockIndent() int

	// BlockIndent sets an indent width on current line.
	// This value is valid only for BlockParser.Open.
	SetBlockIndent(int)

	// FirstDelimiter returns a first delimiter of the current delimiter list.
	FirstDelimiter() *Delimiter

	// LastDelimiter returns a last delimiter of the current delimiter list.
	LastDelimiter() *Delimiter

	// PushDelimiter appends the given delimiter to the tail of the current
	// delimiter list.
	PushDelimiter(delimiter *Delimiter)

	// RemoveDelimiter removes the given delimiter from the current delimiter list.
	RemoveDelimiter(d *Delimiter)

	// ClearDelimiters clears the current delimiter list.
	ClearDelimiters(bottom ast.Node)

	// OpenedBlocks returns a list of nodes that are currently in parsing.
	OpenedBlocks() []Block

	// SetOpenedBlocks sets a list of nodes that are currently in parsing.
	SetOpenedBlocks([]Block)

	// LastOpenedBlock returns a last node that is currently in parsing.
	LastOpenedBlock() Block

	// IsInLinkLabel returns true if current position seems to be in link label.
	IsInLinkLabel() bool
}

// A ContextConfig struct is a data structure that holds configuration of the Context.
type ContextConfig struct {
	IDs IDs
}

// An ContextOption is a functional option type for the Context.
type ContextOption func(*ContextConfig)

// WithIDs is a functional option for the Context.
func WithIDs(ids IDs) ContextOption {
	return func(c *ContextConfig) {
		c.IDs = ids
	}
}

type parseContext struct {
	store         []interface{}
	ids           IDs
	refs          map[string]Reference
	blockOffset   int
	blockIndent   int
	delimiters    *Delimiter
	lastDelimiter *Delimiter
	openedBlocks  []Block
}

// NewContext returns a new Context.
func NewContext(options ...ContextOption) Context {
	cfg := &ContextConfig{
		IDs: newIDs(),
	}
	for _, option := range options {
		option(cfg)
	}

	return &parseContext{
		store:         make([]interface{}, ContextKeyMax+1),
		refs:          map[string]Reference{},
		ids:           cfg.IDs,
		blockOffset:   -1,
		blockIndent:   -1,
		delimiters:    nil,
		lastDelimiter: nil,
		openedBlocks:  []Block{},
	}
}

func (p *parseContext) Get(key ContextKey) interface{} {
	return p.store[key]
}

func (p *parseContext) Set(key ContextKey, value interface{}) {
	p.store[key] = value
}

func (p *parseContext) IDs() IDs {
	return p.ids
}

func (p *parseContext) BlockOffset() int {
	return p.blockOffset
}

func (p *parseContext) SetBlockOffset(v int) {
	p.blockOffset = v
}

func (p *parseContext) BlockIndent() int {
	return p.blockIndent
}

func (p *parseContext) SetBlockIndent(v int) {
	p.blockIndent = v
}

func (p *parseContext) LastDelimiter() *Delimiter {
	return p.lastDelimiter
}

func (p *parseContext) FirstDelimiter() *Delimiter {
	return p.delimiters
}

func (p *parseContext) PushDelimiter(d *Delimiter) {
	if p.delimiters == nil {
		p.delimiters = d
		p.lastDelimiter = d
	} else {
		l := p.lastDelimiter
		p.lastDelimiter = d
		l.NextDelimiter = d
		d.PreviousDelimiter = l
	}
}

func (p *parseContext) RemoveDelimiter(d *Delimiter) {
	if d.PreviousDelimiter == nil {
		p.delimiters = d.NextDelimiter
	} else {
		d.PreviousDelimiter.NextDelimiter = d.NextDelimiter
		if d.NextDelimiter != nil {
			d.NextDelimiter.PreviousDelimiter = d.PreviousDelimiter
		}
	}
	if d.NextDelimiter == nil {
		p.lastDelimiter = d.PreviousDelimiter
	}
	if p.delimiters != nil {
		p.delimiters.PreviousDelimiter = nil
	}
	if p.lastDelimiter != nil {
		p.lastDelimiter.NextDelimiter = nil
	}
	d.NextDelimiter = nil
	d.PreviousDelimiter = nil
	if d.Length != 0 {
		ast.MergeOrReplaceTextSegment(d.Parent(), d, d.Segment)
	} else {
		d.Parent().RemoveChild(d.Parent(), d)
	}
}

func (p *parseContext) ClearDelimiters(bottom ast.Node) {
	if p.lastDelimiter == nil {
		return
	}
	var c ast.Node
	for c = p.lastDelimiter; c != nil && c != bottom; {
		prev := c.PreviousSibling()
		if d, ok := c.(*Delimiter); ok {
			p.RemoveDelimiter(d)
		}
		c = prev
	}
}

func (p *parseContext) AddReference(ref Reference) {
	key := util.ToLinkReference(ref.Label())
	if _, ok := p.refs[key]; !ok {
		p.refs[key] = ref
	}
}

func (p *parseContext) Reference(label string) (Reference, bool) {
	v, ok := p.refs[label]
	return v, ok
}

func (p *parseContext) References() []Reference {
	ret := make([]Reference, 0, len(p.refs))
	for _, v := range p.refs {
		ret = append(ret, v)
	}
	return ret
}

func (p *parseContext) String() string {
	refs := []string{}
	for _, r := range p.refs {
		refs = append(refs, r.String())
	}

	return fmt.Sprintf("Context{Store:%#v, Refs:%s}", p.store, strings.Join(refs, ","))
}

func (p *parseContext) OpenedBlocks() []Block {
	return p.openedBlocks
}

func (p *parseContext) SetOpenedBlocks(v []Block) {
	p.openedBlocks = v
}

func (p *parseContext) LastOpenedBlock() Block {
	if l := len(p.openedBlocks); l != 0 {
		return p.openedBlocks[l-1]
	}
	return Block{}
}

func (p *parseContext) IsInLinkLabel() bool {
	tlist := p.Get(linkLabelStateKey)
	return tlist != nil
}

// State represents parser's state.
// State is designed to use as a bit flag.
type State int

const (
	none State = 1 << iota

	// Continue indicates parser can continue parsing.
	Continue

	// Close indicates parser cannot parse anymore.
	Close

	// HasChildren indicates parser may have child blocks.
	HasChildren

	// NoChildren indicates parser does not have child blocks.
	NoChildren

	// RequireParagraph indicates parser requires that the last node
	// must be a paragraph and is not converted to other nodes by
	// ParagraphTransformers.
	RequireParagraph
)

// A Config struct is a data structure that holds configuration of the Parser.
type Config struct {
	Options               map[OptionName]interface{}
	BlockParsers          util.PrioritizedSlice /*<BlockParser>*/
	InlineParsers         util.PrioritizedSlice /*<InlineParser>*/
	ParagraphTransformers util.PrioritizedSlice /*<ParagraphTransformer>*/
	ASTTransformers       util.PrioritizedSlice /*<ASTTransformer>*/
}

// NewConfig returns a new Config.
func NewConfig() *Config {
	return &Config{
		Options:               map[OptionName]interface{}{},
		BlockParsers:          util.PrioritizedSlice{},
		InlineParsers:         util.PrioritizedSlice{},
		ParagraphTransformers: util.PrioritizedSlice{},
		ASTTransformers:       util.PrioritizedSlice{},
	}
}

// An Option interface is a functional option type for the Parser.
type Option interface {
	SetParserOption(*Config)
}

// OptionName is a name of parser options.
type OptionName string

// Attribute is an option name that spacify attributes of elements.
const optAttribute OptionName = "Attribute"

type withAttribute struct {
}

func (o *withAttribute) SetParserOption(c *Config) {
	c.Options[optAttribute] = true
}

// WithAttribute is a functional option that enables custom attributes.
func WithAttribute() Option {
	return &withAttribute{}
}

// A Parser interface parses Markdown text into AST nodes.
type Parser interface {
	// Parse parses the given Markdown text into AST nodes.
	Parse(reader text.Reader, opts ...ParseOption) ast.Node

	// AddOption adds the given option to this parser.
	AddOptions(...Option)
}

// A SetOptioner interface sets the given option to the object.
type SetOptioner interface {
	// SetOption sets the given option to the object.
	// Unacceptable options may be passed.
	// Thus implementations must ignore unacceptable options.
	SetOption(name OptionName, value interface{})
}

// A BlockParser interface parses a block level element like Paragraph, List,
// Blockquote etc.
type BlockParser interface {
	// Trigger returns a list of characters that triggers Parse method of
	// this parser.
	// If Trigger returns a nil, Open will be called with any lines.
	Trigger() []byte

	// Open parses the current line and returns a result of parsing.
	//
	// Open must not parse beyond the current line.
	// If Open has been able to parse the current line, Open must advance a reader
	// position by consumed byte length.
	//
	// If Open has not been able to parse the current line, Open should returns
	// (nil, NoChildren). If Open has been able to parse the current line, Open
	// should returns a new Block node and returns HasChildren or NoChildren.
	Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State)

	// Continue parses the current line and returns a result of parsing.
	//
	// Continue must not parse beyond the current line.
	// If Continue has been able to parse the current line, Continue must advance
	// a reader position by consumed byte length.
	//
	// If Continue has not been able to parse the current line, Continue should
	// returns Close. If Continue has been able to parse the current line,
	// Continue should returns (Continue | NoChildren) or
	// (Continue | HasChildren)
	Continue(node ast.Node, reader text.Reader, pc Context) State

	// Close will be called when the parser returns Close.
	Close(node ast.Node, reader text.Reader, pc Context)

	// CanInterruptParagraph returns true if the parser can interrupt paragraphs,
	// otherwise false.
	CanInterruptParagraph() bool

	// CanAcceptIndentedLine returns true if the parser can open new node when
	// the given line is being indented more than 3 spaces.
	CanAcceptIndentedLine() bool
}

// An InlineParser interface parses an inline level element like CodeSpan, Link etc.
type InlineParser interface {
	// Trigger returns a list of characters that triggers Parse method of
	// this parser.
	// Trigger characters must be a punctuation or a halfspace.
	// Halfspaces triggers this parser when character is any spaces characters or
	// a head of line
	Trigger() []byte

	// Parse parse the given block into an inline node.
	//
	// Parse can parse beyond the current line.
	// If Parse has been able to parse the current line, it must advance a reader
	// position by consumed byte length.
	Parse(parent ast.Node, block text.Reader, pc Context) ast.Node
}

// A CloseBlocker interface is a callback function that will be
// called when block is closed in the inline parsing.
type CloseBlocker interface {
	// CloseBlock will be called when a block is closed.
	CloseBlock(parent ast.Node, block text.Reader, pc Context)
}

// A ParagraphTransformer transforms parsed Paragraph nodes.
// For example, link references are searched in parsed Paragraphs.
type ParagraphTransformer interface {
	// Transform transforms the given paragraph.
	Transform(node *ast.Paragraph, reader text.Reader, pc Context)
}

// ASTTransformer transforms entire Markdown document AST tree.
type ASTTransformer interface {
	// Transform transforms the given AST tree.
	Transform(node *ast.Document, reader text.Reader, pc Context)
}

// DefaultBlockParsers returns a new list of default BlockParsers.
// Priorities of default BlockParsers are:
//
//     SetextHeadingParser, 100
//     ThematicBreakParser, 200
//     ListParser, 300
//     ListItemParser, 400
//     CodeBlockParser, 500
//     ATXHeadingParser, 600
//     FencedCodeBlockParser, 700
//     BlockquoteParser, 800
//     HTMLBlockParser, 900
//     ParagraphParser, 1000
func DefaultBlockParsers() []util.PrioritizedValue {
	return []util.PrioritizedValue{
		util.Prioritized(NewSetextHeadingParser(), 100),
		util.Prioritized(NewThematicBreakParser(), 200),
		util.Prioritized(NewListParser(), 300),
		util.Prioritized(NewListItemParser(), 400),
		util.Prioritized(NewCodeBlockParser(), 500),
		util.Prioritized(NewATXHeadingParser(), 600),
		util.Prioritized(NewFencedCodeBlockParser(), 700),
		util.Prioritized(NewBlockquoteParser(), 800),
		util.Prioritized(NewHTMLBlockParser(), 900),
		util.Prioritized(NewParagraphParser(), 1000),
	}
}

// DefaultInlineParsers returns a new list of default InlineParsers.
// Priorities of default InlineParsers are:
//
//     CodeSpanParser, 100
//     LinkParser, 200
//     AutoLinkParser, 300
//     RawHTMLParser, 400
//     EmphasisParser, 500
func DefaultInlineParsers() []util.PrioritizedValue {
	return []util.PrioritizedValue{
		util.Prioritized(NewCodeSpanParser(), 100),
		util.Prioritized(NewLinkParser(), 200),
		util.Prioritized(NewAutoLinkParser(), 300),
		util.Prioritized(NewRawHTMLParser(), 400),
		util.Prioritized(NewEmphasisParser(), 500),
	}
}

// DefaultParagraphTransformers returns a new list of default ParagraphTransformers.
// Priorities of default ParagraphTransformers are:
//
//     LinkReferenceParagraphTransformer, 100
func DefaultParagraphTransformers() []util.PrioritizedValue {
	return []util.PrioritizedValue{
		util.Prioritized(LinkReferenceParagraphTransformer, 100),
	}
}

// A Block struct holds a node and correspond parser pair.
type Block struct {
	// Node is a BlockNode.
	Node ast.Node
	// Parser is a BlockParser.
	Parser BlockParser
}

type parser struct {
	options               map[OptionName]interface{}
	blockParsers          [256][]BlockParser
	freeBlockParsers      []BlockParser
	inlineParsers         [256][]InlineParser
	closeBlockers         []CloseBlocker
	paragraphTransformers []ParagraphTransformer
	astTransformers       []ASTTransformer
	config                *Config
	initSync              sync.Once
}

type withBlockParsers struct {
	value []util.PrioritizedValue
}

func (o *withBlockParsers) SetParserOption(c *Config) {
	c.BlockParsers = append(c.BlockParsers, o.value...)
}

// WithBlockParsers is a functional option that allow you to add
// BlockParsers to the parser.
func WithBlockParsers(bs ...util.PrioritizedValue) Option {
	return &withBlockParsers{bs}
}

type withInlineParsers struct {
	value []util.PrioritizedValue
}

func (o *withInlineParsers) SetParserOption(c *Config) {
	c.InlineParsers = append(c.InlineParsers, o.value...)
}

// WithInlineParsers is a functional option that allow you to add
// InlineParsers to the parser.
func WithInlineParsers(bs ...util.PrioritizedValue) Option {
	return &withInlineParsers{bs}
}

type withParagraphTransformers struct {
	value []util.PrioritizedValue
}

func (o *withParagraphTransformers) SetParserOption(c *Config) {
	c.ParagraphTransformers = append(c.ParagraphTransformers, o.value...)
}

// WithParagraphTransformers is a functional option that allow you to add
// ParagraphTransformers to the parser.
func WithParagraphTransformers(ps ...util.PrioritizedValue) Option {
	return &withParagraphTransformers{ps}
}

type withASTTransformers struct {
	value []util.PrioritizedValue
}

func (o *withASTTransformers) SetParserOption(c *Config) {
	c.ASTTransformers = append(c.ASTTransformers, o.value...)
}

// WithASTTransformers is a functional option that allow you to add
// ASTTransformers to the parser.
func WithASTTransformers(ps ...util.PrioritizedValue) Option {
	return &withASTTransformers{ps}
}

type withOption struct {
	name  OptionName
	value interface{}
}

func (o *withOption) SetParserOption(c *Config) {
	c.Options[o.name] = o.value
}

// WithOption is a functional option that allow you to set
// an arbitrary option to the parser.
func WithOption(name OptionName, value interface{}) Option {
	return &withOption{name, value}
}

// NewParser returns a new Parser with given options.
func NewParser(options ...Option) Parser {
	config := NewConfig()
	for _, opt := range options {
		opt.SetParserOption(config)
	}

	p := &parser{
		options: map[OptionName]interface{}{},
		config:  config,
	}

	return p
}

func (p *parser) AddOptions(opts ...Option) {
	for _, opt := range opts {
		opt.SetParserOption(p.config)
	}
}

func (p *parser) addBlockParser(v util.PrioritizedValue, options map[OptionName]interface{}) {
	bp, ok := v.Value.(BlockParser)
	if !ok {
		panic(fmt.Sprintf("%v is not a BlockParser", v.Value))
	}
	tcs := bp.Trigger()
	so, ok := v.Value.(SetOptioner)
	if ok {
		for oname, ovalue := range options {
			so.SetOption(oname, ovalue)
		}
	}
	if tcs == nil {
		p.freeBlockParsers = append(p.freeBlockParsers, bp)
	} else {
		for _, tc := range tcs {
			if p.blockParsers[tc] == nil {
				p.blockParsers[tc] = []BlockParser{}
			}
			p.blockParsers[tc] = append(p.blockParsers[tc], bp)
		}
	}
}

func (p *parser) addInlineParser(v util.PrioritizedValue, options map[OptionName]interface{}) {
	ip, ok := v.Value.(InlineParser)
	if !ok {
		panic(fmt.Sprintf("%v is not a InlineParser", v.Value))
	}
	tcs := ip.Trigger()
	so, ok := v.Value.(SetOptioner)
	if ok {
		for oname, ovalue := range options {
			so.SetOption(oname, ovalue)
		}
	}
	if cb, ok := ip.(CloseBlocker); ok {
		p.closeBlockers = append(p.closeBlockers, cb)
	}
	for _, tc := range tcs {
		if p.inlineParsers[tc] == nil {
			p.inlineParsers[tc] = []InlineParser{}
		}
		p.inlineParsers[tc] = append(p.inlineParsers[tc], ip)
	}
}

func (p *parser) addParagraphTransformer(v util.PrioritizedValue, options map[OptionName]interface{}) {
	pt, ok := v.Value.(ParagraphTransformer)
	if !ok {
		panic(fmt.Sprintf("%v is not a ParagraphTransformer", v.Value))
	}
	so, ok := v.Value.(SetOptioner)
	if ok {
		for oname, ovalue := range options {
			so.SetOption(oname, ovalue)
		}
	}
	p.paragraphTransformers = append(p.paragraphTransformers, pt)
}

func (p *parser) addASTTransformer(v util.PrioritizedValue, options map[OptionName]interface{}) {
	at, ok := v.Value.(ASTTransformer)
	if !ok {
		panic(fmt.Sprintf("%v is not a ASTTransformer", v.Value))
	}
	so, ok := v.Value.(SetOptioner)
	if ok {
		for oname, ovalue := range options {
			so.SetOption(oname, ovalue)
		}
	}
	p.astTransformers = append(p.astTransformers, at)
}

// A ParseConfig struct is a data structure that holds configuration of the Parser.Parse.
type ParseConfig struct {
	Context Context
}

// A ParseOption is a functional option type for the Parser.Parse.
type ParseOption func(c *ParseConfig)

// WithContext is a functional option that allow you to override
// a default context.
func WithContext(context Context) ParseOption {
	return func(c *ParseConfig) {
		c.Context = context
	}
}

func (p *parser) Parse(reader text.Reader, opts ...ParseOption) ast.Node {
	p.initSync.Do(func() {
		p.config.BlockParsers.Sort()
		for _, v := range p.config.BlockParsers {
			p.addBlockParser(v, p.config.Options)
		}
		for i := range p.blockParsers {
			if p.blockParsers[i] != nil {
				p.blockParsers[i] = append(p.blockParsers[i], p.freeBlockParsers...)
			}
		}

		p.config.InlineParsers.Sort()
		for _, v := range p.config.InlineParsers {
			p.addInlineParser(v, p.config.Options)
		}
		p.config.ParagraphTransformers.Sort()
		for _, v := range p.config.ParagraphTransformers {
			p.addParagraphTransformer(v, p.config.Options)
		}
		p.config.ASTTransformers.Sort()
		for _, v := range p.config.ASTTransformers {
			p.addASTTransformer(v, p.config.Options)
		}
		p.config = nil
	})
	c := &ParseConfig{}
	for _, opt := range opts {
		opt(c)
	}
	if c.Context == nil {
		c.Context = NewContext()
	}
	pc := c.Context
	root := ast.NewDocument()
	p.parseBlocks(root, reader, pc)

	blockReader := text.NewBlockReader(reader.Source(), nil)
	p.walkBlock(root, func(node ast.Node) {
		p.parseBlock(blockReader, node, pc)
	})
	for _, at := range p.astTransformers {
		at.Transform(root, reader, pc)
	}
	// root.Dump(reader.Source(), 0)
	return root
}

func (p *parser) transformParagraph(node *ast.Paragraph, reader text.Reader, pc Context) bool {
	for _, pt := range p.paragraphTransformers {
		pt.Transform(node, reader, pc)
		if node.Parent() == nil {
			return true
		}
	}
	return false
}

func (p *parser) closeBlocks(from, to int, reader text.Reader, pc Context) {
	blocks := pc.OpenedBlocks()
	for i := from; i >= to; i-- {
		node := blocks[i].Node
		blocks[i].Parser.Close(blocks[i].Node, reader, pc)
		paragraph, ok := node.(*ast.Paragraph)
		if ok && node.Parent() != nil {
			p.transformParagraph(paragraph, reader, pc)
		}
	}
	if from == len(blocks)-1 {
		blocks = blocks[0:to]
	} else {
		blocks = append(blocks[0:to], blocks[from+1:]...)
	}
	pc.SetOpenedBlocks(blocks)
}

type blockOpenResult int

const (
	paragraphContinuation blockOpenResult = iota + 1
	newBlocksOpened
	noBlocksOpened
)

func (p *parser) openBlocks(parent ast.Node, blankLine bool, reader text.Reader, pc Context) blockOpenResult {
	result := blockOpenResult(noBlocksOpened)
	continuable := false
	lastBlock := pc.LastOpenedBlock()
	if lastBlock.Node != nil {
		continuable = ast.IsParagraph(lastBlock.Node)
	}
retry:
	var bps []BlockParser
	line, _ := reader.PeekLine()
	w, pos := util.IndentWidth(line, reader.LineOffset())
	if w >= len(line) {
		pc.SetBlockOffset(-1)
		pc.SetBlockIndent(-1)
	} else {
		pc.SetBlockOffset(pos)
		pc.SetBlockIndent(w)
	}
	if line == nil || line[0] == '\n' {
		goto continuable
	}
	bps = p.freeBlockParsers
	if pos < len(line) {
		bps = p.blockParsers[line[pos]]
		if bps == nil {
			bps = p.freeBlockParsers
		}
	}
	if bps == nil {
		goto continuable
	}

	for _, bp := range bps {
		if continuable && result == noBlocksOpened && !bp.CanInterruptParagraph() {
			continue
		}
		if w > 3 && !bp.CanAcceptIndentedLine() {
			continue
		}
		lastBlock = pc.LastOpenedBlock()
		last := lastBlock.Node
		node, state := bp.Open(parent, reader, pc)
		if node != nil {
			// Parser requires last node to be a paragraph.
			// With table extension:
			//
			//     0
			//     -:
			//     -
			//
			// '-' on 3rd line seems a Setext heading because 1st and 2nd lines
			// are being paragraph when the Settext heading parser tries to parse the 3rd
			// line.
			// But 1st line and 2nd line are a table. Thus this paragraph will be transformed
			// by a paragraph transformer. So this text should be converted to a table and
			// an empty list.
			if state&RequireParagraph != 0 {
				if last == parent.LastChild() {
					// Opened paragraph may be transformed by ParagraphTransformers in
					// closeBlocks().
					lastBlock.Parser.Close(last, reader, pc)
					blocks := pc.OpenedBlocks()
					pc.SetOpenedBlocks(blocks[0 : len(blocks)-1])
					if p.transformParagraph(last.(*ast.Paragraph), reader, pc) {
						// Paragraph has been transformed.
						// So this parser is considered as failing.
						continuable = false
						goto retry
					}
				}
			}
			node.SetBlankPreviousLines(blankLine)
			if last != nil && last.Parent() == nil {
				lastPos := len(pc.OpenedBlocks()) - 1
				p.closeBlocks(lastPos, lastPos, reader, pc)
			}
			parent.AppendChild(parent, node)
			result = newBlocksOpened
			be := Block{node, bp}
			pc.SetOpenedBlocks(append(pc.OpenedBlocks(), be))
			if state&HasChildren != 0 {
				parent = node
				goto retry // try child block
			}
			break // no children, can not open more blocks on this line
		}
	}

continuable:
	if result == noBlocksOpened && continuable {
		state := lastBlock.Parser.Continue(lastBlock.Node, reader, pc)
		if state&Continue != 0 {
			result = paragraphContinuation
		}
	}
	return result
}

type lineStat struct {
	lineNum int
	level   int
	isBlank bool
}

func isBlankLine(lineNum, level int, stats []lineStat) bool {
	ret := true
	for i := len(stats) - 1 - level; i >= 0; i-- {
		ret = false
		s := stats[i]
		if s.lineNum == lineNum {
			if s.level < level && s.isBlank {
				return true
			} else if s.level == level {
				return s.isBlank
			}
		}
		if s.lineNum < lineNum {
			return ret
		}
	}
	return ret
}

func (p *parser) parseBlocks(parent ast.Node, reader text.Reader, pc Context) {
	pc.SetOpenedBlocks([]Block{})
	blankLines := make([]lineStat, 0, 128)
	isBlank := false
	for { // process blocks separated by blank lines
		_, lines, ok := reader.SkipBlankLines()
		if !ok {
			return
		}
		lineNum, _ := reader.Position()
		if lines != 0 {
			blankLines = blankLines[0:0]
			l := len(pc.OpenedBlocks())
			for i := 0; i < l; i++ {
				blankLines = append(blankLines, lineStat{lineNum - 1, i, lines != 0})
			}
		}
		isBlank = isBlankLine(lineNum-1, 0, blankLines)
		// first, we try to open blocks
		if p.openBlocks(parent, isBlank, reader, pc) != newBlocksOpened {
			return
		}
		reader.AdvanceLine()
		for { // process opened blocks line by line
			openedBlocks := pc.OpenedBlocks()
			l := len(openedBlocks)
			if l == 0 {
				break
			}
			lastIndex := l - 1
			for i := 0; i < l; i++ {
				be := openedBlocks[i]
				line, _ := reader.PeekLine()
				if line == nil {
					p.closeBlocks(lastIndex, 0, reader, pc)
					reader.AdvanceLine()
					return
				}
				lineNum, _ := reader.Position()
				blankLines = append(blankLines, lineStat{lineNum, i, util.IsBlank(line)})
				// If node is a paragraph, p.openBlocks determines whether it is continuable.
				// So we do not process paragraphs here.
				if !ast.IsParagraph(be.Node) {
					state := be.Parser.Continue(be.Node, reader, pc)
					if state&Continue != 0 {
						// When current node is a container block and has no children,
						// we try to open new child nodes
						if state&HasChildren != 0 && i == lastIndex {
							isBlank = isBlankLine(lineNum-1, i, blankLines)
							p.openBlocks(be.Node, isBlank, reader, pc)
							break
						}
						continue
					}
				}
				// current node may be closed or lazy continuation
				isBlank = isBlankLine(lineNum-1, i, blankLines)
				thisParent := parent
				if i != 0 {
					thisParent = openedBlocks[i-1].Node
				}
				lastNode := openedBlocks[lastIndex].Node
				result := p.openBlocks(thisParent, isBlank, reader, pc)
				if result != paragraphContinuation {
					// lastNode is a paragraph and was transformed by the paragraph
					// transformers.
					if openedBlocks[lastIndex].Node != lastNode {
						lastIndex--
					}
					p.closeBlocks(lastIndex, i, reader, pc)
				}
				break
			}

			reader.AdvanceLine()
		}
	}
}

func (p *parser) walkBlock(block ast.Node, cb func(node ast.Node)) {
	for c := block.FirstChild(); c != nil; c = c.NextSibling() {
		p.walkBlock(c, cb)
	}
	cb(block)
}

func (p *parser) parseBlock(block text.BlockReader, parent ast.Node, pc Context) {
	if parent.IsRaw() {
		return
	}
	escaped := false
	source := block.Source()
	block.Reset(parent.Lines())
	for {
	retry:
		line, _ := block.PeekLine()
		if line == nil {
			break
		}
		lineLength := len(line)
		hardlineBreak := false
		softLinebreak := line[lineLength-1] == '\n'
		if lineLength >= 2 && line[lineLength-2] == '\\' && softLinebreak { // ends with \\n
			lineLength -= 2
			hardlineBreak = true

		} else if lineLength >= 3 && line[lineLength-3] == '\\' && line[lineLength-2] == '\r' && softLinebreak { // ends with \\r\n
			lineLength -= 3
			hardlineBreak = true
		} else if lineLength >= 3 && line[lineLength-3] == ' ' && line[lineLength-2] == ' ' && softLinebreak { // ends with [space][space]\n
			lineLength -= 3
			hardlineBreak = true
		} else if lineLength >= 4 && line[lineLength-4] == ' ' && line[lineLength-3] == ' ' && line[lineLength-2] == '\r' && softLinebreak { // ends with [space][space]\r\n
			lineLength -= 4
			hardlineBreak = true
		}

		l, startPosition := block.Position()
		n := 0
		for i := 0; i < lineLength; i++ {
			c := line[i]
			if c == '\n' {
				break
			}
			isSpace := util.IsSpace(c)
			isPunct := util.IsPunct(c)
			if (isPunct && !escaped) || isSpace || i == 0 {
				parserChar := c
				if isSpace || (i == 0 && !isPunct) {
					parserChar = ' '
				}
				ips := p.inlineParsers[parserChar]
				if ips != nil {
					block.Advance(n)
					n = 0
					savedLine, savedPosition := block.Position()
					if i != 0 {
						_, currentPosition := block.Position()
						ast.MergeOrAppendTextSegment(parent, startPosition.Between(currentPosition))
						_, startPosition = block.Position()
					}
					var inlineNode ast.Node
					for _, ip := range ips {
						inlineNode = ip.Parse(parent, block, pc)
						if inlineNode != nil {
							break
						}
						block.SetPosition(savedLine, savedPosition)
					}
					if inlineNode != nil {
						parent.AppendChild(parent, inlineNode)
						goto retry
					}
				}
			}
			if escaped {
				escaped = false
				n++
				continue
			}

			if c == '\\' {
				escaped = true
				n++
				continue
			}

			escaped = false
			n++
		}
		if n != 0 {
			block.Advance(n)
		}
		currentL, currentPosition := block.Position()
		if l != currentL {
			continue
		}
		diff := startPosition.Between(currentPosition)
		stop := diff.Stop
		rest := diff.WithStop(stop)
		text := ast.NewTextSegment(rest.TrimRightSpace(source))
		text.SetSoftLineBreak(softLinebreak)
		text.SetHardLineBreak(hardlineBreak)
		parent.AppendChild(parent, text)
		block.AdvanceLine()
	}

	ProcessDelimiters(nil, pc)
	for _, ip := range p.closeBlockers {
		ip.CloseBlock(parent, block, pc)
	}
}
