// 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 provides CommonMark-compliant markdown parser and renderer.
package markdown

import (
	"bytes"
	"io"
)

type Markdown struct {
	options
	Block         ParserBlock
	Inline        ParserInline
	renderOptions RenderOptions
}

type RenderOptions struct {
	LangPrefix string // CSS language class prefix for fenced blocks
	XHTML      bool   // render as XHTML instead of HTML
	Breaks     bool   // convert \n in paragraphs into <br>
	Nofollow   bool   // add rel="nofollow" to the links
}

type options struct {
	HTML        bool      // allow raw HTML in the markup
	Tables      bool      // GFM tables
	Linkify     bool      // autoconvert URL-like text to links
	Typographer bool      // enable some typographic replacements
	Quotes      [4]string // double/single quotes replacement pairs
	MaxNesting  int       // maximum nesting level
}

type Environment struct {
	References map[string]map[string]string
}

type CoreRule func(*StateCore)

var coreRules []CoreRule

func New(opts ...option) *Markdown {
	m := &Markdown{
		options: options{
			Tables:      true,
			Linkify:     true,
			Typographer: true,
			Quotes:      [4]string{"“", "”", "‘", "’"},
			MaxNesting:  20,
		},
		renderOptions: RenderOptions{LangPrefix: "language-"},
	}
	for _, opt := range opts {
		opt(m)
	}
	return m
}

func (m *Markdown) Parse(src []byte) []Token {
	if len(src) == 0 {
		return nil
	}

	s := &StateCore{
		Md:  m,
		Env: &Environment{},
	}
	s.Tokens = m.Block.Parse(src, m, s.Env)

	for _, r := range coreRules {
		r(s)
	}
	return s.Tokens
}

func (m *Markdown) Render(w io.Writer, src []byte) error {
	if len(src) == 0 {
		return nil
	}

	return NewRenderer(w).Render(m.Parse(src), m.renderOptions)
}

func (m *Markdown) RenderTokens(w io.Writer, tokens []Token) error {
	if len(tokens) == 0 {
		return nil
	}

	return NewRenderer(w).Render(tokens, m.renderOptions)
}

func (m *Markdown) RenderToString(src []byte) string {
	if len(src) == 0 {
		return ""
	}

	var buf bytes.Buffer
	NewRenderer(&buf).Render(m.Parse(src), m.renderOptions)
	return buf.String()
}

func (m *Markdown) RenderTokensToString(tokens []Token) string {
	if len(tokens) == 0 {
		return ""
	}

	var buf bytes.Buffer
	NewRenderer(&buf).Render(tokens, m.renderOptions)
	return buf.String()
}