package main

import (
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
)

const (
	PORT = 61594

	FILES_DIR    = "files"
	TEMPLATES_DIR = "templates"
	TIME_FORMAT = "Jan 2 2006 15:04:05"

	POST_ICON = "/static/post.gif"
	FILE_ICON = "/static/file.gif"
)

var (
	headerPattern     = regexp.MustCompile(`(?m)^# (.+)$`)
	subHeaderPattern  = regexp.MustCompile(`(?m)^## (.+)$`)
	newlinePattern    = regexp.MustCompile(`(?m)^([^#<].+)`)
	boldPattern       = regexp.MustCompile(`\*\*(.*?)\*\*|__(.*?)__`)
	italicPattern     = regexp.MustCompile(`\*(.*?)\*|_(.*?)_`)
	inlineCodePattern = regexp.MustCompile("`([^`]+)`")
	codeBlockPattern  = regexp.MustCompile("(?s)```(.*?)```")
	blockquotePattern = regexp.MustCompile(`(?m)^> (.+)$`)
	ulPattern         = regexp.MustCompile(`(?m)^(?:-|\*) (.+)$`)
	olPattern         = regexp.MustCompile(`(?m)^\d+\. (.+)$`)
	linkPattern       = regexp.MustCompile(`\[(.*?)\]\((.*?)\)`)
)

type FileEntry struct {
	Name      string
	Timestamp string
	ParentPath string
	Icon string
}

type Page struct {
	Title    string
	Content  template.HTML
	Files    []FileEntry
	Folders  []string
	BasePath string
	ParentPath string
	TimeStamp string
}

func listEntries(basePath string) ([]FileEntry, []string) {
	entries, err := os.ReadDir(filepath.Join(FILES_DIR, strings.TrimPrefix(basePath, "/")))
	if err != nil {
		log.Fatal(err)
	}
	var files []FileEntry
	var folders []string
	for _, entry := range entries {
		if entry.IsDir() {
			folders = append(folders, entry.Name())
		} else if strings.HasSuffix(entry.Name(), ".md") {
			info, _ := entry.Info()
			files = append(files, FileEntry{Name: entry.Name(), Timestamp: info.ModTime().Format(TIME_FORMAT), ParentPath: basePath, Icon: POST_ICON})
		}else
		{
			info, _ := entry.Info()
			files = append(files, FileEntry{Name: entry.Name(), Timestamp: info.ModTime().Format(TIME_FORMAT), ParentPath: basePath, Icon: FILE_ICON})
		}
	}
	sort.Slice(files, func(i, j int) bool { return files[i].Name < files[j].Name })
	sort.Strings(folders)
	return files, folders
}

func readMarkdown(filename string) string {
	content, err := os.ReadFile(filepath.Join(FILES_DIR, filename))
	if err != nil {
		log.Fatal(err)
	}
	return string(content)
}


func renderMarkdown(content string) template.HTML {
	content = headerPattern.ReplaceAllString(content, "<h1>$1</h1>")
	content = subHeaderPattern.ReplaceAllString(content, "<h2>$1</h2>")
	content = boldPattern.ReplaceAllString(content, "<b>$1$2</b>")
	content = italicPattern.ReplaceAllString(content, "<i>$1$2</i>")
	content = inlineCodePattern.ReplaceAllString(content, "<code>$1</code>")
	content = codeBlockPattern.ReplaceAllString(content, "<pre><code>$1</code></pre>")
	content = blockquotePattern.ReplaceAllString(content, "<blockquote>$1</blockquote>")
	content = ulPattern.ReplaceAllString(content, "<li>$1</li>")
	content = olPattern.ReplaceAllString(content, "<li>$1</li>")
	content = linkPattern.ReplaceAllString(content, `<a href="$2">$1</a>`)
	content = regexp.MustCompile(`(<li>.+?</li>)`).ReplaceAllString(content, "<ul>$1</ul>")
	content = newlinePattern.ReplaceAllString(content, "<p>$1</p>")

	return template.HTML(content)
}


func loadTemplate(name string) *template.Template {
	tmplPath := filepath.Join(TEMPLATES_DIR, name)
	tmpl, err := template.ParseFiles(tmplPath)
	if err != nil {
		log.Fatal(err)
	}
	return tmpl
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
	basePath := strings.TrimPrefix(r.URL.Path, "/")
	files, folders := listEntries(r.URL.Path)
	page := Page{Files: files, Folders: folders, BasePath: basePath}
	tmpl := loadTemplate("index.html")
	tmpl.Execute(w, page)
}

func postHandler(w http.ResponseWriter, r *http.Request) {
	filename := strings.TrimPrefix(r.URL.Path, "/")

	parentPath := filepath.ToSlash(filepath.Dir(filename) + "/")
	if parentPath == "." {
		parentPath = ""
	}

	info, err := os.Stat(filepath.Join(FILES_DIR, filename))
	if err != nil {
		http.NotFound(w, r)
		return
	}

	timeStamp := info.ModTime().Format(TIME_FORMAT)

	content := readMarkdown(filename)
	page := Page{Title: filename, Content: renderMarkdown(content), ParentPath: parentPath, TimeStamp: timeStamp}
	tmpl := loadTemplate("post.html")
	tmpl.Execute(w, page)
}

func main() {
	if _, err := os.Stat(FILES_DIR); os.IsNotExist(err) {
		os.Mkdir(FILES_DIR, os.ModePerm)
	}

	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
		http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		path := strings.TrimPrefix(r.URL.Path, "/")
		if strings.HasSuffix(path, ".md") {
			postHandler(w, r)
		} else if strings.Contains(path, ".") {
			http.ServeFile(w, r, filepath.Join(FILES_DIR, path))
		} else {
			indexHandler(w, r)
		}
	})

	http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
   		http.ServeFile(w, r, "static/post.gif")
	})


	log.Println("Serving on :", PORT)
	http.ListenAndServe(fmt.Sprintf("127.0.0.1:%d", PORT), nil)
}