// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

// Package main generates cobra.Command go variables containing documentation read from .md files.
// Usage: mdtogo SOURCE_MD_DIR/ DEST_GO_DIR/ [--full=true] [--license=license.txt|none]
//
// The command will create a docs.go file under DEST_GO_DIR/ containing string variables to be
// used by cobra commands for documentation.The variable names are generated from the SOURCE_MD_DIR/
// file names, replacing '-' with '', title casing the filename, and dropping the extension.
// All *.md will be read from DEST_GO_DIR/, and a single DEST_GO_DIR/docs.go file is generated.
//
// Each .md document will be parsed as follows if no flags are provided:
//
//   ## cmd
//
//   This section will be parsed into a string variable for `Short`
//
//   ### Synopsis
//
//   This section will be parsed into a string variable for `Long`
//
//   ### Examples
//
//   This section will be parsed into a string variable for `Example`
//
// If --full=true is provided, the document will be parsed as follows:
//
//   ## cmd
//
//   All sections will be parsed into a Long string.
//
// Flags:
//   --full=true
//     Create a Long variable from the full .md files, rather than separate sections.
//   --license
//     Controls the license header added to the files.  Specify a path to a license file,
//     or "none" to skip adding a license.
package main

import (
	"bufio"
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
)

var full bool
var licenseFile string

func main() {
	for _, a := range os.Args {
		if a == "--full=true" {
			full = true
		}
		if strings.HasPrefix(a, "--license=") {
			licenseFile = strings.ReplaceAll(a, "--license=", "")
		}
	}

	if len(os.Args) < 3 {
		fmt.Fprintf(os.Stderr, "Usage: mdtogo SOURCE_MD_DIR/ DEST_GO_DIR/\n")
		os.Exit(1)
	}
	source := os.Args[1]
	dest := os.Args[2]

	files, err := ioutil.ReadDir(source)
	if err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		os.Exit(1)
	}

	var docs []doc
	for _, f := range files {
		if filepath.Ext(f.Name()) != ".md" {
			continue
		}
		b, err := ioutil.ReadFile(filepath.Join(source, f.Name()))
		if err != nil {
			fmt.Fprintf(os.Stderr, "%v\n", err)
			os.Exit(1)
		}

		docs = append(docs, parse(f.Name(), string(b)))
	}

	var license string

	if licenseFile == "" {
		license = `// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0`
	} else if licenseFile == "none" {
		// no license -- maybe added by another tool
	} else {
		b, err := ioutil.ReadFile(licenseFile)
		if err != nil {
			fmt.Fprintf(os.Stderr, "%v\n", err)
			os.Exit(1)
		}
		license = string(b)
	}

	out := []string{license, `
// Code generated by "mdtogo"; DO NOT EDIT.
package ` + filepath.Base(dest) + "\n"}

	for i := range docs {
		out = append(out, docs[i].String())
	}

	if _, err := os.Stat(dest); err != nil {
		_ = os.Mkdir(dest, 0700)
	}

	o := strings.Join(out, "\n")
	err = ioutil.WriteFile(filepath.Join(dest, "docs.go"), []byte(o), 0600)
	if err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		os.Exit(1)
	}
}

func parse(name, value string) doc {
	name = strings.ReplaceAll(name, filepath.Ext(name), "")
	name = strings.Title(name)
	name = strings.ReplaceAll(name, "-", "")

	scanner := bufio.NewScanner(bytes.NewBufferString(value))

	var long, examples []string
	var short string
	var isLong, isExample, isIndent bool
	var doc doc

	for scanner.Scan() {
		line := scanner.Text()

		if strings.HasPrefix(line, "## ") && short == "" {
			for scanner.Scan() {
				if strings.TrimSpace(scanner.Text()) == "" {
					continue
				}
				short = scanner.Text()
				break
			}
			continue
		}

		if !full {
			if strings.HasPrefix(line, "### Synopsis") {
				isLong = true
				isExample = false
				continue
			}

			if strings.HasPrefix(line, "### Examples") {
				isLong = false
				isExample = true
				continue
			}

			if strings.HasPrefix(line, "### ") {
				isLong = false
				isExample = false
				continue
			}
		}

		if strings.HasPrefix(line, "```") {
			isIndent = !isIndent
			continue
		}
		line = strings.ReplaceAll(line, "`", "` + \"`\" + `")
		if isIndent {
			line = "\t" + line
		}

		if isLong || full {
			long = append(long, line)
			continue
		}
		if isExample {
			examples = append(examples, line)
		}
	}

	doc.Name = name
	doc.Short = short
	doc.Long = strings.Join(long, "\n")
	doc.Examples = strings.Join(examples, "\n")

	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}

	return doc
}

type doc struct {
	Name     string
	Short    string
	Long     string
	Examples string
}

func (d doc) String() string {
	var parts []string

	if d.Short != "" {
		parts = append(parts,
			fmt.Sprintf("var %sShort=`%s`", d.Name, d.Short))
	}
	if d.Long != "" {
		parts = append(parts,
			fmt.Sprintf("var %sLong=`%s`", d.Name, d.Long))
	}
	if d.Examples != "" {
		parts = append(parts,
			fmt.Sprintf("var %sExamples=`%s`", d.Name, d.Examples))
	}

	return strings.Join(parts, "\n") + "\n"
}
