package main

import (
	"bytes"
	"embed"
	"fmt"
	"html/template"
	"io/fs"
	"log"
	"os"
	"path/filepath"
	"sort"

	"github.com/spf13/cobra"
)

const (
	dirPermissions  fs.FileMode = 0755
	filePermissions fs.FileMode = 0666
)

var (
	//go:embed assets/*
	assets embed.FS
)

func init() {
	cmd.Flags().Bool("empty", false, "empty output directory")
	cmd.Flags().Bool("skip-html", false, "do not generate HTML files")
	cmd.Flags().UintP("from", "f", 1, "first comic to download")
	cmd.Flags().UintP("to", "t", 0, "last comic to download")
}

func main() {
	if err := cmd.Execute(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

var cmd = &cobra.Command{
	Use:  "xkcd-archiver output",
	Args: cobra.ExactArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		empty, _ := cmd.Flags().GetBool("empty")
		skipHTML, _ := cmd.Flags().GetBool("skip-html")
		from, _ := cmd.Flags().GetUint("from")
		to, _ := cmd.Flags().GetUint("to")
		out := args[0]

		latest, err := getLatestID()
		if err != nil {
			return err
		}
		log.Printf("Latest comic is %d.\n", latest)

		if from == 0 {
			from = 1
		}

		if to == 0 {
			to = uint(latest)
		}

		if empty {
			err := os.RemoveAll(out)
			if err != nil {
				return err
			}
		}

		err = os.MkdirAll(out, dirPermissions)
		if err != nil {
			return err
		}

		log.Printf("Downloading comics from %d to %d.\n", from, to)

		comicTemplate, err := getTemplate("comic")
		if err != nil {
			return err
		}

		homeTemplate, err := getTemplate("home")
		if err != nil {
			return err
		}

		homeData := &homeData{}

		for id := from; id <= to; id++ {
			if id == 404 {
				// Comic 404 does not exist.
				continue
			}

			log.Printf("Downloading comic %d.\n", id)

			comicDir := filepath.Join(out, fmt.Sprintf("%d", id))
			metadata, err := getComic(comicDir, id)
			if err != nil {
				return err
			}

			data := &comicData{
				Num:    id,
				Title:  metadata.String("title"),
				Alt:    metadata.String("alt"),
				Image:  metadata.String("img"),
				Latest: latest,
			}

			var b bytes.Buffer
			err = comicTemplate.Execute(&b, data)
			if err != nil {
				return err
			}
			err = os.WriteFile(filepath.Join(comicDir, "index.html"), b.Bytes(), filePermissions)
			if err != nil {
				return err
			}

			homeData.Comics = append(homeData.Comics, data)
		}

		if !skipHTML {
			sort.Slice(homeData.Comics, func(i, j int) bool {
				return homeData.Comics[i].Num > homeData.Comics[j].Num
			})

			var b bytes.Buffer
			err = homeTemplate.Execute(&b, homeData)
			if err != nil {
				return err
			}
			err = os.WriteFile(filepath.Join(out, "index.html"), b.Bytes(), filePermissions)
			if err != nil {
				return err
			}

			err = copyAsset(out, "styles.css")
			if err != nil {
				return err
			}

			err = copyAsset(out, "favicon.ico")
			if err != nil {
				return err
			}
		}

		log.Println("Comics fetched.")
		return nil
	},
}

type comicData struct {
	Latest uint
	Num    uint
	Title  string
	Alt    string
	Image  string
}

type homeData struct {
	Comics []*comicData
}

func getTemplate(name string) (*template.Template, error) {
	comicBytes, err := assets.ReadFile("assets/" + name + ".html")
	if err != nil {
		return nil, err
	}
	return template.New("").Funcs(template.FuncMap{
		"minus": func(a, b uint) uint {
			return a - b
		},
		"plus": func(a, b uint) uint {
			return a + b
		},
	}).Parse(string(comicBytes))
}

func copyAsset(out, filename string) error {
	data, err := assets.ReadFile("assets/" + filename)
	if err != nil {
		return err
	}
	return os.WriteFile(filepath.Join(out, filename), data, filePermissions)
}