// +build THIS_IS_BROKEN

package main

import (
	"bytes"
	"encoding/xml"
	"flag"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"strings"

	"camlistore.org/pkg/blobref"
	"camlistore.org/pkg/blobserver/localdisk"
	"camlistore.org/pkg/cacher"
	"camlistore.org/pkg/client"
	"camlistore.org/pkg/fs"

//	"camlistore.org/third_party/github.com/hanwen/go-fuse/fuse"
)

var (
	f       *fs.CamliFileSystem
	davaddr = flag.String("davaddr", "", "WebDAV service address")
)

// TODO(rh): tame copy/paste code from cammount
func main() {
	client.AddFlags()
	flag.Parse()
	cacheDir, err := ioutil.TempDir("", "camlicache")
	if err != nil {
		log.Fatalf("Error creating temp cache directory: %v", err)
	}
	defer os.RemoveAll(cacheDir)
	diskcache, err := localdisk.New(cacheDir)
	if err != nil {
		log.Fatalf("Error setting up local disk cache: %v", err)
	}
	if flag.NArg() != 1 {
		log.Fatal("usage: camwebdav <blobref>")
	}
	br := blobref.Parse(flag.Arg(0))
	if br == nil {
		log.Fatalf("%s was not a valid blobref.", flag.Arg(0))
	}
	client := client.NewOrFail()
	fetcher := cacher.NewCachingFetcher(diskcache, client)

	f = fs.NewCamliFileSystem(fetcher, br)
	http.HandleFunc("/", webdav)
	err = http.ListenAndServe(*davaddr, nil)
	if err != nil {
		log.Fatalf("Error starting WebDAV server: %v", err)
	}
}

func webdav(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "GET":
		get(w, r)
	case "OPTIONS":
		w.Header().Set("DAV", "1")
	case "PROPFIND":
		propfind(w, r)
	default:
		w.WriteHeader(http.StatusBadRequest)
	}
}

// sendHTTPStatus is an HTTP status code.
type sendHTTPStatus int

// senderr, when deferred, recovers panics of
// type sendHTTPStatus and writes the corresponding
// HTTP status to the response.  If the value is not
// of type sendHTTPStatus, it re-panics.
func senderr(w http.ResponseWriter) {
	err := recover()
	if stat, ok := err.(sendHTTPStatus); ok {
		w.WriteHeader(int(stat))
	} else if err != nil {
		panic(err)
	}
}

// GET Method
func get(w http.ResponseWriter, r *http.Request) {
	defer senderr(w)
	file, stat := f.Open(url2path(r.URL), uint32(os.O_RDONLY))

	checkerr(stat)

	w.Header().Set("Content-Type", "application/octet-stream")
	ff, err := file.(*fs.CamliFile).GetReader()
	_, err = io.Copy(w, ff)
	if err != nil {
		log.Print("propfind: error writing response: %s", err)
	}
}

// 9.1 PROPFIND Method
func propfind(w http.ResponseWriter, r *http.Request) {
	defer senderr(w)
	depth := r.Header.Get("Depth")
	switch depth {
	case "0", "1":
	case /*implicit infinity*/ "", "infinity":
		log.Print("propfind: unsupported depth of infinity")
		panic(sendHTTPStatus(http.StatusForbidden))
	default:
		log.Print("propfind: invalid Depth of", depth)
		panic(sendHTTPStatus(http.StatusBadRequest))
	}

	// TODO(rh) Empty body == allprop

	// TODO(rh): allprop
	var propsToFind []string

	x := parsexml(r.Body)
	x.muststart("propfind")
	switch {
	case x.start("propname"):
		x.mustend("propname")
	case x.start("allprop"):
		x.mustend("allprop")
		if x.start("include") {
			// TODO(rh) parse elements
			x.mustend("include")
		}
	case x.start("prop"):
		propsToFind = parseprop(x)
		x.mustend("prop")
	}
	x.mustend("propfind")
	var files = []string{url2path(r.URL)}
	if depth == "1" {
		// TODO(rh) handle bad stat
		files = append(files, ls(files[0])...)
	}

	var ms multistatus
	for _, file := range files {
		resp := &response{href: (*href)(path2url(file))}
		attr, stat := f.GetAttr(file) // TODO(rh) better way?

		checkerr(stat)

		var props []xmler
		for _, p := range propsToFind {
			switch p {
			case "creationdate":
				props = append(props, creationdate(attr.Ctime))
			case "resourcetype":
				props = append(props, resourcetype(attr.Mode&fuse.S_IFDIR == fuse.S_IFDIR))
			case "getcontentlength":
				props = append(props, getcontentlength(attr.Size))
			case "getlastmodified":
				props = append(props, getlastmodified(attr.Mtime))
			}

			resp.body = propstats{{props, 200}}
		}
		ms = append(ms, resp)
	}

	var xmlbytes bytes.Buffer
	ms.XML(&xmlbytes)
	w.Header().Set("Content-Type", "application/xml; charset=UTF-8")
	w.WriteHeader(207) // 207 Multi-Status
	_, err := io.Copy(w, &xmlbytes)
	if err != nil {
		log.Print("propfind: error writing response: %s", err)
	}
}

func checkerr(stat fuse.Status) {
	switch stat {
	case fuse.ENOENT:
		panic(sendHTTPStatus(http.StatusNotFound))
	case fuse.OK:
	default:
		panic(sendHTTPStatus(http.StatusForbidden))
	}
}

func ls(path string) (paths []string) {
	dirs, err := f.OpenDir(path)

	checkerr(err)

	for d := range dirs {
		// TODO(rh) determine a proper way to join paths
		if path != "" {
			d.Name = path + "/" + d.Name
		}
		paths = append(paths, d.Name)
	}
	return
}

// TODO(rh) settle on an internal format for paths, and a better way to translate between paths and URLs
func url2path(url_ *url.URL) string {
	return strings.Trim(url_.Path, "/") // TODO(rh) make not suck
}

func path2url(path string) *url.URL {
	return &url.URL{Path: "/" + path} // TODO(rh) make not suck
}

func parseprop(x *xmlparser) (props []string) {
	for {
		el, ok := x.cur.(xml.StartElement)
		if !ok {
			break
		}
		props = append(props, el.Name.Local)
		x.muststart(el.Name.Local)
		x.mustend(el.Name.Local)
	}
	return
}