package ansi

import (
	"io"

	"github.com/alecthomas/chroma"
	"github.com/alecthomas/chroma/quick"
	"github.com/alecthomas/chroma/styles"
	"github.com/muesli/reflow/indent"
)

// A CodeBlockElement is used to render code blocks.
type CodeBlockElement struct {
	Code     string
	Language string
}

func chromaStyle(style StylePrimitive) string {
	var s string

	if style.Color != nil {
		s = *style.Color
	}
	if style.BackgroundColor != nil {
		if s != "" {
			s += " "
		}
		s += "bg:" + *style.BackgroundColor
	}
	if style.Italic != nil && *style.Italic {
		if s != "" {
			s += " "
		}
		s += "italic"
	}
	if style.Bold != nil && *style.Bold {
		if s != "" {
			s += " "
		}
		s += "bold"
	}
	if style.Underline != nil && *style.Underline {
		if s != "" {
			s += " "
		}
		s += "underline"
	}

	return s
}

func (e *CodeBlockElement) Render(w io.Writer, ctx RenderContext) error {
	bs := ctx.blockStack

	var indentation uint
	var margin uint
	rules := ctx.options.Styles.CodeBlock
	if rules.Indent != nil {
		indentation = *rules.Indent
	}
	if rules.Margin != nil {
		margin = *rules.Margin
	}
	theme := rules.Theme

	if rules.Chroma != nil && ctx.options.ColorProfile > 1 {
		theme = "charm"
		styles.Register(chroma.MustNewStyle("charm",
			chroma.StyleEntries{
				chroma.Text:                chromaStyle(rules.Chroma.Text),
				chroma.Error:               chromaStyle(rules.Chroma.Error),
				chroma.Comment:             chromaStyle(rules.Chroma.Comment),
				chroma.CommentPreproc:      chromaStyle(rules.Chroma.CommentPreproc),
				chroma.Keyword:             chromaStyle(rules.Chroma.Keyword),
				chroma.KeywordReserved:     chromaStyle(rules.Chroma.KeywordReserved),
				chroma.KeywordNamespace:    chromaStyle(rules.Chroma.KeywordNamespace),
				chroma.KeywordType:         chromaStyle(rules.Chroma.KeywordType),
				chroma.Operator:            chromaStyle(rules.Chroma.Operator),
				chroma.Punctuation:         chromaStyle(rules.Chroma.Punctuation),
				chroma.Name:                chromaStyle(rules.Chroma.Name),
				chroma.NameBuiltin:         chromaStyle(rules.Chroma.NameBuiltin),
				chroma.NameTag:             chromaStyle(rules.Chroma.NameTag),
				chroma.NameAttribute:       chromaStyle(rules.Chroma.NameAttribute),
				chroma.NameClass:           chromaStyle(rules.Chroma.NameClass),
				chroma.NameConstant:        chromaStyle(rules.Chroma.NameConstant),
				chroma.NameDecorator:       chromaStyle(rules.Chroma.NameDecorator),
				chroma.NameException:       chromaStyle(rules.Chroma.NameException),
				chroma.NameFunction:        chromaStyle(rules.Chroma.NameFunction),
				chroma.NameOther:           chromaStyle(rules.Chroma.NameOther),
				chroma.Literal:             chromaStyle(rules.Chroma.Literal),
				chroma.LiteralNumber:       chromaStyle(rules.Chroma.LiteralNumber),
				chroma.LiteralDate:         chromaStyle(rules.Chroma.LiteralDate),
				chroma.LiteralString:       chromaStyle(rules.Chroma.LiteralString),
				chroma.LiteralStringEscape: chromaStyle(rules.Chroma.LiteralStringEscape),
				chroma.GenericDeleted:      chromaStyle(rules.Chroma.GenericDeleted),
				chroma.GenericEmph:         chromaStyle(rules.Chroma.GenericEmph),
				chroma.GenericInserted:     chromaStyle(rules.Chroma.GenericInserted),
				chroma.GenericStrong:       chromaStyle(rules.Chroma.GenericStrong),
				chroma.GenericSubheading:   chromaStyle(rules.Chroma.GenericSubheading),
				chroma.Background:          chromaStyle(rules.Chroma.Background),
			}))
	}

	iw := indent.NewWriterPipe(w, indentation+margin, func(wr io.Writer) {
		renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, " ")
	})

	if len(theme) > 0 {
		renderText(iw, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.BlockPrefix)
		err := quick.Highlight(iw, e.Code, e.Language, "terminal256", theme)
		if err != nil {
			return err
		}
		renderText(iw, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.BlockSuffix)
		return nil
	}

	// fallback rendering
	el := &BaseElement{
		Token: e.Code,
		Style: rules.StylePrimitive,
	}

	return el.Render(iw, ctx)
}
