package template

var filesTemplate = `{{buildTags .Tags}}// Code generated by fileb0x at "{{.Now}}" from config file "{{.ConfigFile}}" DO NOT EDIT.
// modification hash({{.ModificationHash}})

package {{.Pkg}}
{{$Compression := .Compression}}

import (
  "bytes"
  {{if not .Spread}}{{if and $Compression.Compress (not .Debug)}}{{if not $Compression.Keep}}"compress/gzip"{{end}}{{end}}{{end}}
  "context"
  "io"
  "net/http"
  "os"
  "path"
{{if or .Updater.Enabled .Debug}}
  "strings"
{{end}}

  "golang.org/x/net/webdav"

{{if .Updater.Enabled}}
  "crypto/sha256"
	"encoding/hex"
  "log"
  "path/filepath"

	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"
{{end}}
)

var ( 
  // CTX is a context for webdav vfs
  {{exported "CTX"}} = context.Background()

  {{if .Debug}}
  {{exported "FS"}} = webdav.Dir(".")
  {{else}}
  // FS is a virtual memory file system
  {{exported "FS"}} = webdav.NewMemFS()
  {{end}}

  // Handler is used to server files through a http handler
  {{exportedTitle "Handler"}} *webdav.Handler

  // HTTP is the http file system
  {{exportedTitle "HTTP"}} http.FileSystem = new({{exported "HTTPFS"}})
)

// HTTPFS implements http.FileSystem
type {{exported "HTTPFS"}} struct {
	// Prefix allows to limit the path of all requests. F.e. a prefix "css" would allow only calls to /css/*
	Prefix string
}

{{if (and (not .Spread) (not .Debug))}}
{{range .Files}}
// {{exportedTitle "File"}}{{buildSafeVarName .Path}} is "{{.Path}}"
var {{exportedTitle "File"}}{{buildSafeVarName .Path}} = {{.Data}}
{{end}}
{{end}}

func init() {
  err := {{exported "CTX"}}.Err()
  if err != nil {
		panic(err)
	}

{{ $length := len .DirList }}
{{ $fLength := len .Files }}
{{ $noDirsButFiles := (and (not .Spread) (eq $length 0) (gt $fLength 0)) }}
{{if not .Debug}}
{{range $index, $dir := .DirList}}
  {{if and (ne $dir "./") (ne $dir "/") (ne $dir ".") (ne $dir "")}}
  err = {{exported "FS"}}.Mkdir({{exported "CTX"}}, "{{$dir}}", 0777)
  if err != nil && err != os.ErrExist {
    panic(err)
  }
  {{end}}
{{end}}
{{end}}

{{if (and (not .Spread) (not .Debug))}}
  {{if not .Updater.Empty}}
  var f webdav.File
  {{end}}

  {{if $Compression.Compress}}
  {{if not $Compression.Keep}}
  var rb *bytes.Reader
  var r *gzip.Reader
  {{end}}
  {{end}}

  {{range .Files}}
  {{if $Compression.Compress}}
  {{if not $Compression.Keep}}
  rb = bytes.NewReader({{exportedTitle "File"}}{{buildSafeVarName .Path}})
  r, err = gzip.NewReader(rb)
  if err != nil {
    panic(err)
  }

  err = r.Close()
  if err != nil {
    panic(err)
  }
  {{end}}
  {{end}}

  f, err = {{exported "FS"}}.OpenFile({{exported "CTX"}}, "{{.Path}}", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
  if err != nil {
    panic(err)
  }

  {{if $Compression.Compress}}
  {{if not $Compression.Keep}}
  _, err = io.Copy(f, r)
  if err != nil {
    panic(err)
  }
  {{end}}
  {{else}}
  _, err = f.Write({{exportedTitle "File"}}{{buildSafeVarName .Path}})
  if err != nil {
    panic(err)
  }
  {{end}}

  err = f.Close()
  if err != nil {
    panic(err)
  }
  {{end}}
{{end}}

  {{exportedTitle "Handler"}} = &webdav.Handler{
    FileSystem: FS,
    LockSystem: webdav.NewMemLS(),
  }

{{if .Updater.Enabled}}
  go func() {
    svr := &{{exportedTitle "Server"}}{}
    svr.Init()
  }()
{{end}}
}

{{if .Debug}}
var remap = map[string]map[string]string{
  {{.Remap}}
}
{{end}}

// Open a file
func (hfs *{{exported "HTTPFS"}}) Open(path string) (http.File, error) {
  path = hfs.Prefix + path

{{if .Debug}}
  path = strings.TrimPrefix(path, "/")

  for current, f := range remap {
    if path == current {
      path = f["base"] + strings.TrimPrefix(path, f["prefix"])
      break
    }
  }

{{end}}
  f, err := {{if .Debug}}os{{else}}{{exported "FS"}}{{end}}.OpenFile({{if not .Debug}}{{exported "CTX"}}, {{end}}path, os.O_RDONLY, 0644)
  if err != nil {
    return nil, err
  }

  return f, nil
}

// ReadFile is adapTed from ioutil
func {{exportedTitle "ReadFile"}}(path string) ([]byte, error) {
  f, err := {{if .Debug}}os{{else}}{{exported "FS"}}{{end}}.OpenFile({{if not .Debug}}{{exported "CTX"}}, {{end}}path, os.O_RDONLY, 0644)
  if err != nil {
    return nil, err
  }

  buf := bytes.NewBuffer(make([]byte, 0, bytes.MinRead))

  // If the buffer overflows, we will get bytes.ErrTooLarge.
  // Return that as an error. Any other panic remains.
  defer func() {
    e := recover()
    if e == nil {
      return
    }
    if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
      err = panicErr
    } else {
      panic(e)
    }
  }()
  _, err = buf.ReadFrom(f)
  return buf.Bytes(), err
}

// WriteFile is adapTed from ioutil
func {{exportedTitle "WriteFile"}}(filename string, data []byte, perm os.FileMode) error {
  f, err := {{exported "FS"}}.OpenFile({{exported "CTX"}}, filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
  if err != nil {
    return err
  }
  n, err := f.Write(data)
  if err == nil && n < len(data) {
    err = io.ErrShortWrite
  }
  if err1 := f.Close(); err == nil {
    err = err1
  }
  return err
}

// WalkDirs looks for files in the given dir and returns a list of files in it
// usage for all files in the b0x: WalkDirs("", false)
func {{exportedTitle "WalkDirs"}}(name string, includeDirsInList bool, files ...string) ([]string, error) {
	f, err := {{exported "FS"}}.OpenFile({{exported "CTX"}}, name, os.O_RDONLY, 0)
	if err != nil {
		return nil, err
	}

	fileInfos, err := f.Readdir(0)
	if err != nil {
    return nil, err
  }
  
  err = f.Close()
  if err != nil {
		return nil, err
	}

	for _, info := range fileInfos {
		filename := path.Join(name, info.Name())

		if includeDirsInList || !info.IsDir() {
			files = append(files, filename)
		}

		if info.IsDir() {
			files, err = {{exportedTitle "WalkDirs"}}(filename, includeDirsInList, files...)
			if err != nil {
				return nil, err
			}
		}
	}

	return files, nil
}

{{if .Updater.Enabled}}
// Auth holds information for a http basic auth
type {{exportedTitle "Auth"}} struct {
  Username string
  Password string
}

// ResponseInit holds a list of hashes from the server
// to be sent to the client so it can check if there
// is a new file or a changed file
type {{exportedTitle "ResponseInit"}} struct {
  Success bool
  Hashes  map[string]string
}

// Server holds information about the http server
// used to update files remotely
type {{exportedTitle "Server"}} struct {
  Auth {{exportedTitle "Auth"}}
  Files []string
}

// Init sets the routes and basic http auth 
// before starting the http server
func (s *{{exportedTitle "Server"}}) Init() {
  s.Auth = {{exportedTitle "Auth"}}{
    Username: "{{.Updater.Username}}",
    Password: "{{.Updater.Password}}",
  }

  e := echo.New()
  e.Use(middleware.Recover())
  e.Use(s.BasicAuth())
  e.POST("/", s.Post)
  e.GET("/", s.Get)

  log.Println("fileb0x updater server is running at port 0.0.0.0:{{.Updater.Port}}")
  if err := e.Start(":{{.Updater.Port}}"); err != nil {
    panic(err)
  }
}

// Get gives a list of file names and hashes
func (s *{{exportedTitle "Server"}}) Get(c echo.Context) error {
  log.Println("[fileb0x.Server]: Hashing server files...")
  
  // file:hash
  hashes := map[string]string{}

  // get all files in the virtual memory file system
  var err error
  s.Files, err = {{exportedTitle "WalkDirs"}}("", false)
  if err != nil {
    return err
  }

  // get a hash for each file
  for _, filePath := range s.Files {
    f, err := FS.OpenFile(CTX, filePath, os.O_RDONLY, 0644)
    if err != nil {
      return err
    }

    hash := sha256.New()
    _, err = io.Copy(hash, f)
    if err != nil {
      return err
    }

    hashes[filePath] = hex.EncodeToString(hash.Sum(nil))
  }

  log.Println("[fileb0x.Server]: Done hashing files")
  return c.JSON(http.StatusOK, &ResponseInit{
    Success: true,
    Hashes: hashes,
  })
}

// Post is used to upload a file and replace 
// it in the virtual memory file system
func (s *{{exportedTitle "Server"}}) Post(c echo.Context) error {
  file, err := c.FormFile("file")
	if err != nil {
		return err
	}

  log.Println("[fileb0x.Server]:", file.Filename, "Found request to upload a file")

	src, err := file.Open()
	if err != nil {
		return err
	}
	defer src.Close()


  newDir := filepath.Dir(file.Filename)
  _, err = {{exported "FS"}}.Stat({{exported "CTX"}}, newDir)
  if err != nil && strings.HasSuffix(err.Error(), os.ErrNotExist.Error()) {
    log.Println("[fileb0x.Server]: Creating dir tree", newDir)
    list := strings.Split(newDir, "/")
    var tree string
    
    for _, dir := range list {
      if dir == "" || dir == "." || dir == "/" || dir == "./" {
        continue
      }

      tree += dir + "/"
      err = {{exported "FS"}}.Mkdir({{exported "CTX"}}, tree, 0777)
      if err != nil && err != os.ErrExist {
        log.Println("failed", err)
        return err
      }
    }
  }

  log.Println("[fileb0x.Server]:", file.Filename, "Opening file...")
  f, err := {{exported "FS"}}.OpenFile({{exported "CTX"}}, file.Filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
  if err != nil && !strings.HasSuffix(err.Error(), os.ErrNotExist.Error()) {
    return err
  }

  log.Println("[fileb0x.Server]:", file.Filename, "Writing file into Virutal Memory FileSystem...")
  if _, err = io.Copy(f, src); err != nil {
		return err
	}

  if err = f.Close(); err != nil {
    return err
  }

  log.Println("[fileb0x.Server]:", file.Filename, "Done writing file")
  return c.String(http.StatusOK, "ok")
}

// BasicAuth is a middleware to check if 
// the username and password are valid
// echo's middleware isn't used because of golint issues
func (s *{{exportedTitle "Server"}}) BasicAuth() echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			u, p, _ := c.Request().BasicAuth()
			if u != s.Auth.Username || p != s.Auth.Password {
				return echo.ErrUnauthorized
			}

			return next(c)
		}
	}
}
{{end}}
`
