package resource

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"net/http"
	"time"
)

type OneResource struct {
	Path  string
	Ctype string
	Bytes []byte
	MTime int64
}

type Resource struct {
	list map[string]OneResource
}

func New() *Resource {
	r := Resource{}
	r.list = map[string]OneResource{}
	return &r
}

func etag(str string) string {
	hasher := md5.New()
	hasher.Write([]byte(str))
	return hex.EncodeToString(hasher.Sum(nil))
}

func modified(p string, s int, v int64, w http.ResponseWriter, r *http.Request) bool {
	w.Header().Set("Cache-Control", "no-cache")

	// Set: ETag
	ehash := etag(fmt.Sprintf("%s-%d-%d", p, s, v))
	w.Header().Set("ETag", fmt.Sprintf("%s", ehash))

	// Set: Last-Modified
	w.Header().Set(
		"Last-Modified",
		time.Unix(v, 0).In(time.FixedZone("GMT", 0)).Format("Wed, 01 Oct 2006 15:04:05 GMT"),
	)

	// Check: ETag
	if cc := r.Header.Get("Cache-Control"); cc != "no-cache" {
		if inm := r.Header.Get("If-None-Match"); inm == ehash {
			w.WriteHeader(http.StatusNotModified)
			return false
		}
	}

	// Check: Last-Modified
	if cc := r.Header.Get("Cache-Control"); cc != "no-cache" {
		if ims := r.Header.Get("If-Modified-Since"); ims != "" {
			if t, err := time.Parse("Wed, 01 Oct 2006 15:04:05 GMT", ims); err == nil {
				if time.Unix(v, 0).In(time.FixedZone("GMT", 0)).Unix() <= t.In(time.FixedZone("GMT", 0)).Unix() {
					w.WriteHeader(http.StatusNotModified)
					return false
				}
			}
		}
	}

	return true
}

func (this *Resource) Add(path string, ctype string, bytes []byte, mtime int64) {
	// Do not add if already in resources list
	if _, ok := this.list[path]; ok == true {
		return
	}

	// Add to resources list
	this.list[path] = OneResource{
		Path:  path,
		Ctype: ctype,
		Bytes: bytes,
		MTime: mtime,
	}
}

func (this *Resource) Response(w http.ResponseWriter, r *http.Request, before func(w http.ResponseWriter, r *http.Request, i *OneResource), after func(w http.ResponseWriter, r *http.Request, i *OneResource)) bool {
	// Do not process if this is not necessary
	if len(r.URL.Path) <= 1 {
		return false
	}

	// Check for resource
	res, ok := this.list[r.URL.Path[1:]]
	if ok == false {
		return false
	}

	// Cache headers
	w.Header().Set("Content-Type", res.Ctype)
	if !modified(r.URL.Path, len(res.Bytes), res.MTime, w, r) {
		return true
	}

	// Call `before` callback
	if before != nil {
		before(w, r, &res)
	}

	// Send resource
	w.Write(res.Bytes)

	// Call `after` callback
	if after != nil {
		after(w, r, &res)
	}

	return true
}