package markdown

import (
	"bytes"
	"fmt"
	"time"
)

var (
	// Date format YYYY-MM-DD HH:MM:SS or YYYY-MM-DD
	timeLayout = []string{
		`2006-01-02 15:04:05`,
		`2006-01-02`,
	}
)

// Metadata stores a page's metadata
type Metadata struct {
	// Page title
	Title string

	// Page template
	Template string

	// Publish date
	Date time.Time

	// Variables to be used with Template
	Variables map[string]string

	// Flags to be used with Template
	Flags map[string]bool
}

// load loads parsed values in parsedMap into Metadata
func (m *Metadata) load(parsedMap map[string]interface{}) {

	// Pull top level things out
	if title, ok := parsedMap["title"]; ok {
		m.Title, _ = title.(string)
	}
	if template, ok := parsedMap["template"]; ok {
		m.Template, _ = template.(string)
	}
	if date, ok := parsedMap["date"].(string); ok {
		for _, layout := range timeLayout {
			if t, err := time.Parse(layout, date); err == nil {
				m.Date = t
				break
			}
		}
	}

	// Store everything as a flag or variable
	for key, val := range parsedMap {
		switch v := val.(type) {
		case bool:
			m.Flags[key] = v
		case string:
			m.Variables[key] = v
		}
	}
}

// MetadataParser is a an interface that must be satisfied by each parser
type MetadataParser interface {
	// Opening identifier
	Opening() []byte

	// Closing identifier
	Closing() []byte

	// Parse the metadata.
	// Returns the remaining page contents (Markdown)
	// after extracting metadata
	Parse([]byte) ([]byte, error)

	// Parsed metadata.
	// Should be called after a call to Parse returns no error
	Metadata() Metadata
}

// extractMetadata separates metadata content from from markdown content in b.
// It returns the metadata, the remaining bytes (markdown), and an error, if any.
func extractMetadata(parser MetadataParser, b []byte) (metadata []byte, markdown []byte, err error) {
	b = bytes.TrimSpace(b)
	openingLine := parser.Opening()
	closingLine := parser.Closing()
	if !bytes.HasPrefix(b, openingLine) {
		return nil, b, fmt.Errorf("first line missing expected metadata identifier")
	}
	metaStart := len(openingLine)
	if _, ok := parser.(*JSONMetadataParser); ok {
		metaStart = 0
	}
	metaEnd := bytes.Index(b[metaStart:], closingLine)
	if metaEnd == -1 {
		return nil, nil, fmt.Errorf("metadata not closed ('%s' not found)", parser.Closing())
	}
	metaEnd += metaStart
	if _, ok := parser.(*JSONMetadataParser); ok {
		metaEnd += len(closingLine)
	}
	metadata = b[metaStart:metaEnd]
	markdown = b[metaEnd:]
	if _, ok := parser.(*JSONMetadataParser); !ok {
		markdown = b[metaEnd+len(closingLine):]
	}
	return metadata, markdown, nil
}

// findParser finds the parser using line that contains opening identifier
func findParser(b []byte) MetadataParser {
	var line []byte
	// Read first line
	if _, err := fmt.Fscanln(bytes.NewReader(b), &line); err != nil {
		return nil
	}
	line = bytes.TrimSpace(line)
	for _, parser := range parsers() {
		if bytes.Equal(parser.Opening(), line) {
			return parser
		}
	}
	return nil
}

func newMetadata() Metadata {
	return Metadata{
		Variables: make(map[string]string),
		Flags:     make(map[string]bool),
	}
}

// parsers returns all available parsers
func parsers() []MetadataParser {
	return []MetadataParser{
		&JSONMetadataParser{metadata: newMetadata()},
		&TOMLMetadataParser{metadata: newMetadata()},
		&YAMLMetadataParser{metadata: newMetadata()},
	}
}