package sipasm

import (
	"encoding/base64"
	"fmt"
	"strings"
	"time"

	uuid "github.com/satori/go.uuid"
	"gitlab.com/ics_cinnamon/voicegateway/icspacketparser"
)

const (
	MAX_HEADER_OVERLAP int    = 100
	MAX_CSEQ           uint32 = 4294967294
)

const (
	SDP_SENDRECV = 0
	SDP_SENDONLY = 1
	SDP_RECVONLY = 2
)

///////////////////////////////
type headerOverlap struct {
	length  int
	headers [MAX_HEADER_OVERLAP]string
}

func NewHeaderOverlap() *headerOverlap {
	var h headerOverlap
	h.length = 0

	return &h
}

func (h *headerOverlap) AddHeader(msg string) {
	h.headers[h.length] = msg
	h.length++
}

// if idx is less than 0, then returns last header
func (h headerOverlap) GetHeader(idx int) string {
	if idx < 0 {
		return h.headers[h.length]
	} else {
		return h.headers[idx]
	}
}

func (h headerOverlap) GetLength() int {
	return h.length
}

/////////////////////////////////
type SIPMessage struct {
	Method string
	header map[ICSSIP_HEADER_TYPE]*headerOverlap

	HeaderString string
	BodyString   string

	branch string
	tag    string

	recvedSIP *icspacketparser.SIP

	SDP *SDPMessage
}

func NewSIPMessage(methodType ICSSIP_METHOD_TYPE, value string) *SIPMessage {
	if methodType >= ICSSIP_METHOD_MAX {
		return nil
	}

	var sipmessage SIPMessage

	sipmessage.header = make(map[ICSSIP_HEADER_TYPE]*headerOverlap)
	for iter := 0; iter < MAX_HEADER_OVERLAP; iter++ {
		sipmessage.header[ICSSIP_HEADER_TYPE(iter)] = NewHeaderOverlap()
	}

	msg := fmt.Sprintf("%s SIP/2.0", value)
	sipmessage.Method = fmt.Sprintf(ICSSIP_METHOD_STRING[methodType], msg)

	return &sipmessage
}

func NewSIPResponse(sip *icspacketparser.SIP, statusCode int) *SIPMessage {
	var sipmessage SIPMessage

	sipmessage.header = make(map[ICSSIP_HEADER_TYPE]*headerOverlap)
	for iter := 0; iter < MAX_HEADER_OVERLAP; iter++ {
		sipmessage.header[ICSSIP_HEADER_TYPE(iter)] = NewHeaderOverlap()
	}

	sipmessage.recvedSIP = sip

	msg := ICSSIP_STATUSCODE_STRING[ICSSIP_STATUSCODE_TYPE(statusCode)]
	allow := "REGISTER,OPTIONS,INVITE,ACK,CANCEL,BYE,NOTIFY,PRACK,REFER,INFO,SUBSCRIBE,UPDATE"

	//msg := fmt.Sprintf("%s", ICSSIP_STATUSCODE_STRING[ICSSIP_STATUSCODE_TYPE(statusCode)])
	sipmessage.Method = fmt.Sprintf(ICSSIP_METHOD_STRING[ICSSIP_METHOD_SIP20], msg)
	for iter := 0; iter < len(sip.Via); iter++ {
		sipmessage.AddSIPHeader(ICSSIP_HEADER_VIA, sip.Via[iter])
	}
	//maxforwards := fmt.Sprintf("%d", sip.MaxForwards)
	//sipmessage.AddSIPHeader(ICSSIP_HEADER_MAX_FORWARDS, maxforwards)
	sipmessage.AddSIPHeader(ICSSIP_HEADER_FROM, sip.From)
	var toTag string
	if statusCode != 100 && !strings.Contains(sip.To, ";tag") {
		toTag = fmt.Sprintf("%s;tag=%s", sip.To, GenerateTag())
		sipmessage.AddSIPHeader(ICSSIP_HEADER_TO, toTag)
		//fmt.Printf("##### sip.to: %s, toTag: %s\n", sip.To, toTag)
	} else {
		sipmessage.AddSIPHeader(ICSSIP_HEADER_TO, sip.To)
	}
	sipmessage.AddSIPHeader(ICSSIP_HEADER_CALL_ID, sip.CallID)
	sipmessage.AddSIPHeader(ICSSIP_HEADER_CSEQ, sip.Cseq)
	sipmessage.AddSIPHeader(ICSSIP_HEADER_ALLOW, allow)

	return &sipmessage
}

/////////////////////////////////
//SIP operator
func (s *SIPMessage) AddSIPHeader(headerType ICSSIP_HEADER_TYPE, value ...interface{}) string {
	if headerType >= ICSSIP_HEADER_MAX {
		return ""
	}

	var msg string
	if len(value) <= 0 {
		msg = fmt.Sprint(ICSSIP_HEADER_STRING[headerType])
	} else {
		msg = fmt.Sprintf(ICSSIP_HEADER_STRING[headerType], value...)
		//fmt.Printf(ICSSIP_HEADER_STRING[headerType], value...)
	}
	//fmt.Printf(">>>>%s, %s\n", ICSSIP_HEADER_STRING[headerType], value...)

	h := s.header[headerType]
	h.AddHeader(msg)

	return h.GetHeader(-1)
}

func (s SIPMessage) SearchSIPHeader(headerType ICSSIP_HEADER_TYPE) *headerOverlap {
	if headerType < ICSSIP_HEADER_TYPE(0) {
		return nil
	}

	h := s.header[headerType]

	length := h.GetLength()
	for iter := 0; iter < length; iter++ {
		f := h.GetHeader(iter)
		if strings.Contains(ICSSIP_HEADER_STRING[headerType], f) {
			return h
		}
	}

	return nil
}

func (s SIPMessage) SearchSIPHeaderWithString(headerName string) *headerOverlap {
	if len(headerName) <= 0 {
		return nil
	}

	MSG := strings.ToUpper(headerName)
	htype := ICSSIP_HEADER_STRING2TYPE[MSG]
	h := s.header[htype]

	length := h.GetLength()
	for iter := 0; iter < length; iter++ {
		f := h.GetHeader(iter)
		if strings.Contains(ICSSIP_HEADER_STRING[htype], f) {
			return h
		}
	}

	return nil
}

//if idxOfOverlap is less than 0, then deletes whole the HeaderOverlap for that headerType
func (s *SIPMessage) DelSIPHeader(headerType ICSSIP_HEADER_TYPE, idxOfOverlap int) bool {
	if headerType < ICSSIP_HEADER_TYPE(0) {
		return false
	}

	if idxOfOverlap < 0 {
		h := s.SearchSIPHeader(headerType)
		if h == nil {
			return false
		}
		length := h.GetLength()
		for iter := 0; iter < length; iter++ {
			h.headers[iter] = ""
		}
		h.length = 0
	} else {
		h := s.SearchSIPHeader(headerType)
		if h == nil {
			return false
		}
		h.headers[idxOfOverlap] = ""
		h.length--
	}

	return true
}

//replaces a header to point the headerType to value. it replace only first(0th) overlapped header in the HeaderOverlap
//DO NOT USE for VIA header
func (s *SIPMessage) ReplaceSIPHeader(headerType ICSSIP_HEADER_TYPE, value string) string {
	if headerType < ICSSIP_HEADER_TYPE(0) {
		return ""
	}

	if len(value) <= 0 {
		return ""
	}

	h := s.SearchSIPHeader(headerType)
	h.headers[0] = value

	return h.headers[0]
}

func (s *SIPMessage) GetSIPHeader(headerType ICSSIP_HEADER_TYPE) (headers *[MAX_HEADER_OVERLAP]string) {
	if headerType < ICSSIP_HEADER_TYPE(0) {
		return headers
	}

	h := s.header[headerType].headers
	return &h
}

func (s SIPMessage) String() (message string) {
	if len(s.Method) == 0 {
		return ""
	}

	message = s.Method
	for iter := 0; iter < int(ICSSIP_HEADER_MAX); iter++ {
		h := s.header[ICSSIP_HEADER_TYPE(iter)]
		length := h.GetLength()
		for iter2 := 0; iter2 < length; iter2++ {
			msg := h.GetHeader(iter2)
			if len(msg) != 0 {
				message += msg
			}
		}
	}

	if s.SDP != nil {
		message += s.SDP.String()
	}

	/* TODO: add payload to message
	if len(s.Header[ICSSIP_HEADER_CONTENT_TYPE]) != 0 {
	}
	*/

	return message
}

//////////////////////////////////
// SDP operator
type SDPMessage struct {
	connection  string
	origin      string
	sessionName string
	media       []*SDPMedia
}

type SDPMedia struct {
	mediaType ICSSDP_MEDIA_TYPE
	port      int
	rtpmap    []*RTPMap
}

type RTPMap struct {
	payloadNum int
	ptime      int
	fmtp       string
	sendrecv   string
}

//only audio media allowed
func NewSDPMessage(ip string, port int, audFormats []string, sendrecv int) *SDPMessage {
	var sdpMessage SDPMessage

	sessID := time.Now().Local().Unix()
	sessVer := sessID + 1
	oLine := fmt.Sprintf("AudiocodesGW-AGENT %d %d IN IP4 %s", sessID, sessVer, ip)
	connection := fmt.Sprintf("IN IP4 %s", ip)

	sdpMessage = SDPMessage{connection: connection,
		origin:      oLine,
		sessionName: ""}

	sdpMessage.media = make([]*SDPMedia, 1)
	sdpMessage.media[0] = NewSDPMedia(ICSSDP_MEDIA_AUDIO, port, audFormats, sendrecv)

	return &sdpMessage
}

func (s SDPMessage) String() string {
	sdp := fmt.Sprintf("v=0\r\no=%s\r\ns=%s\r\nc=%s\r\nt=0 0\r\n%s", s.origin, s.sessionName, s.connection, s.media[0].String())

	return sdp
}

func NewSDPMedia(audvid ICSSDP_MEDIA_TYPE, port int, audFormats []string, sendrecv int) *SDPMedia {
	sdpMedia := SDPMedia{mediaType: audvid,
		port: port}

	fmtLen := len(audFormats)
	sdpMedia.rtpmap = make([]*RTPMap, fmtLen+1)

	strSendrecv := "sendrecv"
	if sendrecv == SDP_SENDONLY {
		strSendrecv = "sendonly"
	} else if sendrecv == SDP_RECVONLY {
		strSendrecv = "recvonly"
	}

	var iter int
	for iter = 0; iter < fmtLen; iter++ {
		format := strings.ToUpper(audFormats[iter])
		//fmt.Println("set rtpmap>>>", format)
		switch format {
		case "PCMU":
			sdpMedia.rtpmap[iter] = NewRTPMap(0, 20, "", strSendrecv)
		case "PCMA":
			sdpMedia.rtpmap[iter] = NewRTPMap(8, 20, "", strSendrecv)
		case "G729":
			fallthrough
		case "G.729":
			sdpMedia.rtpmap[iter] = NewRTPMap(18, 20, "annexb=no", strSendrecv)
		default:
		}
	}
	//telephone event
	sdpMedia.rtpmap[iter] = NewRTPMap(101, 20, "0-15", "sendrecv")

	return &sdpMedia
}

func (m SDPMedia) String() string {
	var media string

	var payloadNum string
	var rtpmap string
	plen := len(m.rtpmap)
	for iter := 0; iter < plen; iter++ {
		payloadNum += fmt.Sprintf("%d ", m.rtpmap[iter].payloadNum)
		rtpmap += m.rtpmap[iter].String()
	}
	media = fmt.Sprintf("m=audio %d RTP/AVP %s\r\n%s", m.port, payloadNum, rtpmap)

	return media
}

func NewRTPMap(payloadNum int, ptime int, fmtp string, sendrecv string) *RTPMap {
	rtpMap := RTPMap{
		payloadNum: payloadNum,
		ptime:      ptime,
		fmtp:       fmtp,
		sendrecv:   sendrecv,
	}

	return &rtpMap
}

func (r RTPMap) String() string {
	var name string
	switch r.payloadNum {
	case 0:
		name = "PCMU"
	case 8:
		name = "PCMA"
	case 18:
		name = "G729"
	case 101:
		name = "telephone-event"
	}

	var rtpmap string
	if r.payloadNum == 101 {
		rtpmap = fmt.Sprintf("a=rtpmap:%d %s/8000\r\na=fmtp:%d %s\r\n",
			r.payloadNum, name, r.payloadNum, r.fmtp)
	} else {
		if len(r.fmtp) > 0 {
			rtpmap = fmt.Sprintf("a=rtpmap:%d %s/8000\r\na=fmtp:%d %s\r\na=ptime:%d\r\na=%s\r\n",
				r.payloadNum, name, r.payloadNum, r.fmtp, r.ptime, r.sendrecv)
		} else {
			rtpmap = fmt.Sprintf("a=rtpmap:%d %s/8000\r\na=ptime:%d\r\na=%s\r\n",
				r.payloadNum, name, r.ptime, r.sendrecv)
		}
	}
	return rtpmap
}

//////////////////////////////////
func GenerateCallID(postfix string) (callid string) {
	uuid := uuid.NewV4()
	callid = fmt.Sprintf("%s%s", uuid.String(), postfix)

	return callid
}

func GenerateBranch() (branch string) {
	uuid := uuid.NewV4()
	branch = base64.StdEncoding.EncodeToString([]byte(uuid.String()))

	b := []byte(branch)
	length := len(b)
	for iter := 0; iter < length; iter++ {
		if b[iter] == 0x3d {
			b[iter] = '-'
		}
	}
	branch = string(b)

	return branch
}

func GenerateTag() (tag string) {
	now := fmt.Sprintf("%d", time.Now().UnixNano())
	t := base64.StdEncoding.EncodeToString([]byte(now))

	b := []byte(t)
	length := len(b)
	for iter := 0; iter < length; iter++ {
		if b[iter] == 0x3d {
			b[iter] = '-'
		}
	}
	tag = string(b)

	return tag
}
