package middleware
import (
	"fmt"
	"html/template"
	"net/http"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"strings"
	"github.com/labstack/echo"
	"github.com/labstack/gommon/bytes"
)
type (
	// StaticConfig defines the config for Static middleware.
	StaticConfig struct {
		// Skipper defines a function to skip middleware.
		Skipper Skipper
		// Root directory from where the static content is served.
		// Required.
		Root string `yaml:"root"`
		// Index file for serving a directory.
		// Optional. Default value "index.html".
		Index string `yaml:"index"`
		// Enable HTML5 mode by forwarding all not-found requests to root so that
		// SPA (single-page application) can handle the routing.
		// Optional. Default value false.
		HTML5 bool `yaml:"html5"`
		// Enable directory browsing.
		// Optional. Default value false.
		Browse bool `yaml:"browse"`
	}
)
const html = `
  
  
  
  {{ .Name }}
  
	
	
		{{ range .Files }}
		- 
		{{ if .Dir }}
			{{ $name := print .Name "/" }}
			{{ $name }}
			{{ else }}
			{{ .Name }}
			{{ .Size }}
		{{ end }}
		
 
		{{ end }}
  
`
var (
	// DefaultStaticConfig is the default Static middleware config.
	DefaultStaticConfig = StaticConfig{
		Skipper: DefaultSkipper,
		Index:   "index.html",
	}
)
// Static returns a Static middleware to serves static content from the provided
// root directory.
func Static(root string) echo.MiddlewareFunc {
	c := DefaultStaticConfig
	c.Root = root
	return StaticWithConfig(c)
}
// StaticWithConfig returns a Static middleware with config.
// See `Static()`.
func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
	// Defaults
	if config.Root == "" {
		config.Root = "." // For security we want to restrict to CWD.
	}
	if config.Skipper == nil {
		config.Skipper = DefaultStaticConfig.Skipper
	}
	if config.Index == "" {
		config.Index = DefaultStaticConfig.Index
	}
	// Index template
	t, err := template.New("index").Parse(html)
	if err != nil {
		panic(fmt.Sprintf("echo: %v", err))
	}
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) (err error) {
			if config.Skipper(c) {
				return next(c)
			}
			p := c.Request().URL.Path
			if strings.HasSuffix(c.Path(), "*") { // When serving from a group, e.g. `/static*`.
				p = c.Param("*")
			}
			p, err = url.PathUnescape(p)
			if err != nil {
				return
			}
			name := filepath.Join(config.Root, path.Clean("/"+p)) // "/"+ for security
			fi, err := os.Stat(name)
			if err != nil {
				if os.IsNotExist(err) {
					if err = next(c); err != nil {
						if he, ok := err.(*echo.HTTPError); ok {
							if config.HTML5 && he.Code == http.StatusNotFound {
								return c.File(filepath.Join(config.Root, config.Index))
							}
						}
						return
					}
				}
				return
			}
			if fi.IsDir() {
				index := filepath.Join(name, config.Index)
				fi, err = os.Stat(index)
				if err != nil {
					if config.Browse {
						return listDir(t, name, c.Response())
					}
					if os.IsNotExist(err) {
						return next(c)
					}
					return
				}
				return c.File(index)
			}
			return c.File(name)
		}
	}
}
func listDir(t *template.Template, name string, res *echo.Response) (err error) {
	file, err := os.Open(name)
	if err != nil {
		return
	}
	files, err := file.Readdir(-1)
	if err != nil {
		return
	}
	// Create directory index
	res.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
	data := struct {
		Name  string
		Files []interface{}
	}{
		Name: name,
	}
	for _, f := range files {
		data.Files = append(data.Files, struct {
			Name string
			Dir  bool
			Size string
		}{f.Name(), f.IsDir(), bytes.Format(f.Size())})
	}
	return t.Execute(res, data)
}