package main

import (
	"fmt"
	"flag"
	"archive/tar"
	"compress/gzip"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"html/template"
	"io"
	"io/ioutil"
	"time"
	"strings"
)

const pwdlen int = 32
var salt []byte = nil

type IndexHandler struct {
	docPath string
	binPath string
}

type File struct {
	Info os.FileInfo
	Path string
}

func format(t time.Time) string {
	return t.Format("2006-01-02 15:04")
}

func (handler IndexHandler) writeDirToTar(tw *tar.Writer, file os.FileInfo, name string) {
	//TODO use filepath.Walk

	//hdr := &tar.Header{
		//Name: name,
		//Mode: int64(file.Mode()),
		//ModTime: file.ModTime(),
		//Typeflag: tar.TypeDir,
	//}
	//if err := tw.WriteHeader(hdr); err != nil {
		//log.Fatal(err)
	//}
	p := filepath.Join(handler.docPath, name)
	fileInfos, err := os.ReadDir(p)
	if err != nil {
		panic(err)
	}
	for _, finfo := range fileInfos {
		info, err := finfo.Info()
		if err != nil {
			panic(err)
		}
		path := filepath.Join(name, info.Name())
		var link string
		if info.Mode()&os.ModeSymlink == os.ModeSymlink {
			if link, err = os.Readlink(path); err != nil {
				panic(err)
			}
		}
		path = strings.TrimPrefix(path, "/")

		if info.IsDir() {
			handler.writeDirToTar(tw, info, path)
		} else {
			hdr, err := tar.FileInfoHeader(info, link)
			if err != nil {
				log.Fatal(err)
			}
			if err := tw.WriteHeader(hdr); err != nil {
				log.Fatal(err)
			}
			if !info.Mode().IsRegular() {
				continue
			}
			absFilePath := filepath.Join(handler.docPath, path)
			openFile, err := os.Open(absFilePath)
			if err != nil {
				panic(err)
			}
			defer openFile.Close()
			_, err = io.Copy(tw, openFile)
			if err != nil {
				panic(err)
			}
		}
	}
}

func (handler IndexHandler) TarArchive(w io.Writer, r *http.Request, s string) {
	p := filepath.Join(handler.docPath, s)
	f, err := os.Stat(p)
	if err != nil {
		log.Println(err)
		return
	}
	if f.IsDir() {
		s = strings.TrimPrefix(s, "/")
		//gw := gzip.NewWriter(w)
		//defer gw.Close()
		var tw *tar.Writer
		tw = tar.NewWriter(w)
		defer tw.Close()
		handler.writeDirToTar(tw, f, s)
		//w.WriteHeader(http.StatusCreated)
	} else {
		return
	}
}

func (handler IndexHandler) IsExistingIndex() (bool, string) {
	ind := filepath.Join(handler.docPath, "index.html")
	f, err := os.Stat(ind)
	if err == nil && !f.IsDir() {
		return true, ind
	}
	ind = filepath.Join(handler.docPath, "index.htm")
	f, err = os.Stat(ind)
	if err == nil && !f.IsDir() {
		return true, ind
	}
	return false, ""
}

func (handler IndexHandler) ServeDir(w http.ResponseWriter, r *http.Request, dir string) {
	fileInfos, err := ioutil.ReadDir(dir)
	if err != nil {
		panic(err)
	}
	var files []File  = nil
	for _, info := range fileInfos {
		path := filepath.Join(r.URL.Path, info.Name())
		files = append(files, File{info, path})
	}
	funcs := template.FuncMap {
		"format": format,
	}
	tmpl, err := template.New("index.html").Funcs(funcs).ParseFiles(GetIndexPath())
	if err != nil {
		panic(err)
	}
	data := struct {
		DocDir string
		CurrentPath string
		Parent string
		Files []File
	}{
		filepath.Base(handler.docPath),
		r.URL.Path,
		filepath.Dir(r.URL.Path),
		files,
	}
	tmpl.Execute(w, data)
}

func (handler IndexHandler) Execute(w http.ResponseWriter, r *http.Request, s string) {
	b := filepath.Join(handler.binPath, s)
	_, err := os.Stat(b)
	if err != nil {
		log.Println("stat")
		log.Println(err)
		return
	}
	log.Printf("Executing %s\n", b)
	//out, err := exec.Command(b).CombinedOutput()
	cmd := exec.Command(b)
	out, err := cmd.StdoutPipe()
	if err != nil {
		log.Println(err)
		return
	}
	if err := cmd.Start(); err != nil {
		log.Println(err)
		return
	}
	//_, err = w.Write(out)
	_, err = io.Copy(w, out)
	if err != nil {
		log.Println(err)
		return
	}
}

func (handler IndexHandler) ServeFile(w http.ResponseWriter, r *http.Request, p string) {
	http.ServeFile(w, r, p)
}

func (handler IndexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if strings.HasPrefix(r.URL.Path, "/tararchive") {
		s := strings.TrimPrefix(r.URL.Path, "/tararchive")
		contentDisposition := fmt.Sprintf("attachment; filename=\"%v\"", filepath.Base(s) + ".tar")
		log.Println(contentDisposition)
		w.Header().Set("Content-Disposition", contentDisposition)
		handler.TarArchive(w, r, s)
		return
	} else if strings.HasPrefix(r.URL.Path, "/gzarchive") {
		s := strings.TrimPrefix(r.URL.Path, "/gzarchive")
		contentDisposition := fmt.Sprintf("attachment; filename=\"%v\"", filepath.Base(s) + ".tar.gz")
		log.Println(contentDisposition)
		w.Header().Set("Content-Disposition", contentDisposition)
		gw := gzip.NewWriter(w)
		defer gw.Close()
		handler.TarArchive(gw, r, s)
		return
	} else if r.URL.Path == "/ai_execute" && handler.binPath != "" {
		handler.ServeDir(w, r, handler.binPath)
		return
	} else if strings.HasPrefix(r.URL.Path, "/ai_execute") {
		s := strings.TrimPrefix(r.URL.Path, "/ai_execute")
		handler.Execute(w, r, s)
		return
	}

	p := filepath.Join(handler.docPath, r.URL.Path)
	if r.URL.Path == "/" {
		if ok, ind := handler.IsExistingIndex(); ok && r.URL.Path == "/" {
			http.ServeFile(w, r, ind)
			return
		}
	}
	f, err := os.Stat(p)
	if err != nil {
		log.Println(err)
		w.WriteHeader(http.StatusNotFound)
		return
	}
	if f.IsDir() {
		fileInfos, err := ioutil.ReadDir(p)
		if err != nil {
			panic(err)
		}
		var files []File  = nil
		for _, info := range fileInfos {
			path := filepath.Join(r.URL.Path, info.Name())
			files = append(files, File{info, path})
		}
		funcs := template.FuncMap {
			"format": format,
		}
		tmpl, err := template.New("index.html").Funcs(funcs).ParseFiles(GetIndexPath())
		if err != nil {
			panic(err)
		}
		data := struct {
			DocDir string
			CurrentPath string
			Parent string
			Files []File
		}{
			filepath.Base(handler.docPath),
			r.URL.Path,
			filepath.Dir(r.URL.Path),
			files,
		}
		tmpl.Execute(w, data)
	} else {
		handler.ServeFile(w, r, p)
	}
}

func usage() {
	fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [OPTIONS] pathtodocdir\n", os.Args[0])
	fmt.Fprint(flag.CommandLine.Output(), "\nOptions:\n")
	flag.PrintDefaults()
}

func main() {
	port := flag.Int("p", 8080, "The port to bind to.")
	binPath := flag.String("b", "", "The autoindex executables directory.")
	flag.Parse()
	args := flag.Args()
	if len(args) != 1 {
		usage()
		os.Exit(1)
	}
	handler := IndexHandler{args[0], *binPath}
	log.Printf("Listening on port %d with docPath=%s", *port, handler.docPath)
	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", *port), handler))
}
