package web

import (
	"fmt"
	"log"
	"net/http"
	"sync"
)

// mLayer is a single middleware stack layer. It contains a canonicalized
// middleware representation, as well as the original function as passed to us.
type mLayer struct {
	fn   func(*C, http.Handler) http.Handler
	orig interface{}
}

// mStack is an entire middleware stack. It contains a slice of middleware
// layers (outermost first) protected by a mutex, a cache of pre-built stack
// instances, and a final routing function.
type mStack struct {
	lock   sync.Mutex
	stack  []mLayer
	pool   *cPool
	router internalRouter
}

type internalRouter interface {
	route(*C, http.ResponseWriter, *http.Request)
}

/*
cStack is a cached middleware stack instance. Constructing a middleware stack
involves a lot of allocations: at the very least each layer will have to close
over the layer after (inside) it and a stack N levels deep will incur at least N
separate allocations. Instead of doing this on every request, we keep a pool of
pre-built stacks around for reuse.
*/
type cStack struct {
	C
	m    http.Handler
	pool *cPool
}

func (s *cStack) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	s.C = C{}
	s.m.ServeHTTP(w, r)
}
func (s *cStack) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) {
	s.C = c
	s.m.ServeHTTP(w, r)
}

const unknownMiddleware = `Unknown middleware type %T. See http://godoc.org/github.com/zenazn/goji/web#MiddlewareType for a list of acceptable types.`

func (m *mStack) appendLayer(fn interface{}) {
	ml := mLayer{orig: fn}
	switch f := fn.(type) {
	case func(http.Handler) http.Handler:
		ml.fn = func(c *C, h http.Handler) http.Handler {
			return f(h)
		}
	case func(*C, http.Handler) http.Handler:
		ml.fn = f
	default:
		log.Fatalf(unknownMiddleware, fn)
	}
	m.stack = append(m.stack, ml)
}

func (m *mStack) findLayer(l interface{}) int {
	for i, middleware := range m.stack {
		if funcEqual(l, middleware.orig) {
			return i
		}
	}
	return -1
}

func (m *mStack) invalidate() {
	m.pool = makeCPool()
}

func (m *mStack) newStack() *cStack {
	cs := cStack{}
	router := m.router

	cs.m = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		router.route(&cs.C, w, r)
	})
	for i := len(m.stack) - 1; i >= 0; i-- {
		cs.m = m.stack[i].fn(&cs.C, cs.m)
	}

	return &cs
}

func (m *mStack) alloc() *cStack {
	p := m.pool
	cs := p.alloc()
	if cs == nil {
		cs = m.newStack()
	}

	cs.pool = p
	return cs
}

func (m *mStack) release(cs *cStack) {
	cs.C = C{}
	if cs.pool != m.pool {
		return
	}
	p := cs.pool
	cs.pool = nil
	p.release(cs)
}

func (m *mStack) Use(middleware interface{}) {
	m.lock.Lock()
	defer m.lock.Unlock()
	m.appendLayer(middleware)
	m.invalidate()
}

func (m *mStack) Insert(middleware, before interface{}) error {
	m.lock.Lock()
	defer m.lock.Unlock()
	i := m.findLayer(before)
	if i < 0 {
		return fmt.Errorf("web: unknown middleware %v", before)
	}

	m.appendLayer(middleware)
	inserted := m.stack[len(m.stack)-1]
	copy(m.stack[i+1:], m.stack[i:])
	m.stack[i] = inserted

	m.invalidate()
	return nil
}

func (m *mStack) Abandon(middleware interface{}) error {
	m.lock.Lock()
	defer m.lock.Unlock()
	i := m.findLayer(middleware)
	if i < 0 {
		return fmt.Errorf("web: unknown middleware %v", middleware)
	}

	copy(m.stack[i:], m.stack[i+1:])
	m.stack = m.stack[: len(m.stack)-1 : len(m.stack)]

	m.invalidate()
	return nil
}
