package sockjs

import (
	"encoding/json"
	"fmt"
	"math/rand"
	"net/http"
	"sync"
	"time"
)

var (
	entropy      *rand.Rand
	entropyMutex sync.Mutex
)

func init() {
	entropy = rand.New(rand.NewSource(time.Now().UnixNano()))
}

// Options type is used for defining various sockjs options
type Options struct {
	// Transports which don't support cross-domain communication natively ('eventsource' to name one) use an iframe trick.
	// A simple page is served from the SockJS server (using its foreign domain) and is placed in an invisible iframe.
	// Code run from this iframe doesn't need to worry about cross-domain issues, as it's being run from domain local to the SockJS server.
	// This iframe also does need to load SockJS javascript client library, and this option lets you specify its url (if you're unsure,
	// point it to the latest minified SockJS client release, this is the default). You must explicitly specify this url on the server
	// side for security reasons - we don't want the possibility of running any foreign javascript within the SockJS domain (aka cross site scripting attack).
	// Also, sockjs javascript library is probably already cached by the browser - it makes sense to reuse the sockjs url you're using in normally.
	SockJSURL string
	// Most streaming transports save responses on the client side and don't free memory used by delivered messages.
	// Such transports need to be garbage-collected once in a while. `response_limit` sets a minimum number of bytes that can be send
	// over a single http streaming request before it will be closed. After that client needs to open new request.
	// Setting this value to one effectively disables streaming and will make streaming transports to behave like polling transports.
	// The default value is 128K.
	ResponseLimit uint32
	// Some load balancers don't support websockets. This option can be used to disable websockets support by the server. By default websockets are enabled.
	Websocket bool
	// In order to keep proxies and load balancers from closing long running http requests we need to pretend that the connection is active
	// and send a heartbeat packet once in a while. This setting controls how often this is done.
	// By default a heartbeat packet is sent every 25 seconds.
	HeartbeatDelay time.Duration
	// The server closes a session when a client receiving connection have not been seen for a while.
	// This delay is configured by this setting.
	// By default the session is closed when a receiving connection wasn't seen for 5 seconds.
	DisconnectDelay time.Duration
	// Some hosting providers enable sticky sessions only to requests that have JSessionID cookie set.
	// This setting controls if the server should set this cookie to a dummy value.
	// By default setting JSessionID cookie is disabled. More sophisticated behaviour can be achieved by supplying a function.
	JSessionID func(http.ResponseWriter, *http.Request)
}

// DefaultOptions is a convenient set of options to be used for sockjs
var DefaultOptions = Options{
	Websocket:       true,
	JSessionID:      nil,
	SockJSURL:       "http://cdn.sockjs.org/sockjs-0.3.min.js",
	HeartbeatDelay:  25 * time.Second,
	DisconnectDelay: 5 * time.Second,
	ResponseLimit:   128 * 1024,
}

type info struct {
	Websocket    bool     `json:"websocket"`
	CookieNeeded bool     `json:"cookie_needed"`
	Origins      []string `json:"origins"`
	Entropy      int32    `json:"entropy"`
}

func (options *Options) info(rw http.ResponseWriter, req *http.Request) {
	switch req.Method {
	case "GET":
		rw.Header().Set("Content-Type", "application/json; charset=UTF-8")
		json.NewEncoder(rw).Encode(info{
			Websocket:    options.Websocket,
			CookieNeeded: options.JSessionID != nil,
			Origins:      []string{"*:*"},
			Entropy:      generateEntropy(),
		})
	case "OPTIONS":
		rw.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET")
		rw.Header().Set("Access-Control-Max-Age", fmt.Sprintf("%d", 365*24*60*60))
		rw.WriteHeader(http.StatusNoContent) // 204
	default:
		http.NotFound(rw, req)
	}
}

// DefaultJSessionID is a default behaviour function to be used in options for JSessionID if JSESSIONID is needed
func DefaultJSessionID(rw http.ResponseWriter, req *http.Request) {
	cookie, err := req.Cookie("JSESSIONID")
	if err == http.ErrNoCookie {
		cookie = &http.Cookie{
			Name:  "JSESSIONID",
			Value: "dummy",
		}
	}
	cookie.Path = "/"
	header := rw.Header()
	header.Add("Set-Cookie", cookie.String())
}

func (options *Options) cookie(rw http.ResponseWriter, req *http.Request) {
	if options.JSessionID != nil { // cookie is needed
		options.JSessionID(rw, req)
	}
}

func generateEntropy() int32 {
	entropyMutex.Lock()
	entropy := entropy.Int31()
	entropyMutex.Unlock()
	return entropy
}
