package ajp13

import (
	"encoding/binary"
	"fmt"
	"net"
	"reflect"
)

// ForwardRequest marks that this request is an actual request
const ForwardRequest = 2

// Shutdown marks that this is a shutdown request
const Shutdown = 7

// Ping marks that this is a ping request
const Ping = 8

// CPing marks that this is a cping request
const CPing = 10

// AJP13 represents the apache java protocol
type AJP13 struct {
	len        uint16
	Type       uint8
	method     HTTPMethod
	Version    string // e.g. HTTP/1.1
	URI        string
	RemoteAddr net.IP
	RemoteHost string
	Server     string
	Port       uint16
	SSL        bool
	headers    map[string]string
}

// Parse takes a []byte payload and builds
func Parse(payload []byte) (*AJP13, error) {
	if !reflect.DeepEqual(payload[0:2], []byte{0x12, 0x34}) {
		return nil, fmt.Errorf("The magic signature is not 0x12 0x34 but %v", payload[0:2])
	}

	a := &AJP13{}

	// the data length
	a.len = binary.BigEndian.Uint16(payload[2:4])

	if len(payload) < int(a.len+4) {
		return nil, fmt.Errorf("The payload should be %d bytes long is actually only %d bytes long", a.len+4, len(payload))
	}
	data := payload[4 : a.len+4]

	// the request type
	a.Type = uint8(data[0])

	var err error

	switch a.Type {
	case ForwardRequest:
		err = a.parseForwardRequest(data)
		if err != nil {
			return nil, err
		}
	}

	return a, nil
}

func checkLength(data []byte, minLen int, location string) error {
	dataLen := len(data)
	if dataLen < minLen {
		return fmt.Errorf("%s: need %d bytes but have %d", location, minLen, dataLen)
	}

	return nil
}

func (a *AJP13) parseForwardRequest(data []byte) error {

	// Method
	a.method = HTTPMethod(uint8(data[1]))

	offset := uint16(2)

	// Version
	if !reflect.DeepEqual(data[offset:offset+2], []byte{0xff, 0xff}) {
		versionLen := binary.BigEndian.Uint16(data[offset : offset+2])

		if err := checkLength(data, int(offset+2+versionLen+1), "parseForwardRequest/Version"); err != nil {
			return err
		}

		a.Version = string(data[offset+2 : offset+2+versionLen])
		offset += versionLen + 1
	}
	offset += 2

	// URI
	if !reflect.DeepEqual(data[offset:offset+2], []byte{0xff, 0xff}) {
		uriLen := binary.BigEndian.Uint16(data[offset : offset+2])

		if err := checkLength(data, int(offset+2+uriLen+1), "parseForwardRequest/URI"); err != nil {
			return err
		}

		a.URI = string(data[offset+2 : offset+2+uriLen])
		offset += uriLen + 1
	}
	offset += 2

	// RemoteAddr
	if !reflect.DeepEqual(data[offset:offset+2], []byte{0xff, 0xff}) {
		raddrLen := binary.BigEndian.Uint16(data[offset : offset+2])

		if err := checkLength(data, int(offset+2+raddrLen+1), "parseForwardRequest/RemoteAddr"); err != nil {
			return err
		}

		a.RemoteAddr = net.ParseIP(string(data[offset+2 : offset+2+raddrLen]))
		offset += raddrLen + 1
	}
	offset += 2

	// RemoteHost
	if !reflect.DeepEqual(data[offset:offset+2], []byte{0xff, 0xff}) {
		rhostLen := binary.BigEndian.Uint16(data[offset : offset+2])

		if err := checkLength(data, int(offset+2+rhostLen+1), "parseForwardRequest/RemoteHost"); err != nil {
			return err
		}

		a.RemoteHost = string(data[offset+2 : offset+2+rhostLen])
		offset += rhostLen + 1
	}
	offset += 2

	// Server
	if !reflect.DeepEqual(data[offset:offset+2], []byte{0xff, 0xff}) {
		len := binary.BigEndian.Uint16(data[offset : offset+2])

		if err := checkLength(data, int(offset+2+len+1), "parseForwardRequest/Server"); err != nil {
			return err
		}

		a.Server = string(data[offset+2 : offset+2+len])
		offset += len + 1
	}
	offset += 2

	// Port
	a.Port = binary.BigEndian.Uint16(data[offset : offset+2])

	offset += 2

	// SSL
	if data[offset] == 0x1 {
		a.SSL = true
	}

	offset++

	// Headers
	numHeaders := binary.BigEndian.Uint16(data[offset : offset+2])
	offset += 2

	headers := make(map[string]string)

	for idx := uint16(0); idx < numHeaders; idx++ {
		if data[offset] == 0xa0 { // encoded header name
			len := binary.BigEndian.Uint16(data[offset+2 : offset+4])
			hdrName := HTTPHeader(binary.BigEndian.Uint16(data[offset : offset+2])).String()
			if hdrName != "UNKNOWN" {
				headers[hdrName] = string(data[offset+4 : offset+4+len])
			}
			offset = offset + 5 + len
		} else { // plain text header name
			length := binary.BigEndian.Uint16(data[offset : offset+2])
			key := string(data[offset+2 : offset+2+length])

			offset = offset + 3 + length

			length = binary.BigEndian.Uint16(data[offset : offset+2])
			val := string(data[offset+2 : offset+2+length])
			headers[key] = val

			offset = offset + 3 + length
		}
	}

	a.headers = headers

	// don't need Attributes for the time being

	return nil
}

// Length returns the length of the data this AJP13 request consists of
func (a *AJP13) Length() uint16 {
	return a.len
}

// Method returns the name of the method used
func (a *AJP13) Method() string {
	return a.method.String()
}

// Header returns the header value refered to by key
func (a *AJP13) Header(key string) (string, bool) {
	val, ok := a.headers[key]
	return val, ok
}

// Headers returns all headers
func (a *AJP13) Headers() map[string]string {
	return a.headers
}
