package icspacketparser

import (
	"fmt"
	"strconv"
	"strings"

	"gitlab.com/ics_cinnamon/voicegateway/icserror"
	"gitlab.com/ics_cinnamon/voicegateway/icsnet"
)

type IcsSipMethod int

const (
	ICS_SIP_METHOD_REGISTER IcsSipMethod = iota
	ICS_SIP_METHOD_INVITE
	ICS_SIP_METHOD_PUBLISH
	ICS_SIP_METHOD_OPTIONS
	ICS_SIP_METHOD_INFO
	ICS_SIP_METHOD_UPDATE
	ICS_SIP_METHOD_REFER
	ICS_SIP_METHOD_SUBSCRIBE
	ICS_SIP_METHOD_MESSAGE
	ICS_SIP_METHOD_NOTIFY
	ICS_SIP_METHOD_PRACK
	ICS_SIP_METHOD_ACK
	ICS_SIP_METHOD_BYE
	ICS_SIP_METHOD_CANCEL
	ICS_SIP_METHOD_SIP20 //response
	ICS_SIP_METHOD_NOT_FOUND
)

func (arr IcsSipMethod) String() string {
	var strSipMethod = []string{
		"ICS_SIP_METHOD_REGISTER",
		"ICS_SIP_METHOD_INVITE",
		"ICS_SIP_METHOD_PUBLISH",
		"ICS_SIP_METHOD_OPTIONS",
		"ICS_SIP_METHOD_INFO",
		"ICS_SIP_METHOD_UPDATE",
		"ICS_SIP_METHOD_REFER",
		"ICS_SIP_METHOD_SUBSCRIBE",
		"ICS_SIP_METHOD_MESSAGE",
		"ICS_SIP_METHOD_NOTIFY",
		"ICS_SIP_METHOD_PRACK",
		"ICS_SIP_METHOD_ACK",
		"ICS_SIP_METHOD_BYE",
		"ICS_SIP_METHOD_CANCEL",
		"ICS_SIP_METHOD_SIP20",
		"ICS_SIP_METHOD_NOT_FOUND",
	}

	return strSipMethod[int(arr)%len(strSipMethod)]
}

type SIP struct {
	Headers map[string][]string

	Method  IcsSipMethod
	Version string
	Source  string

	Via         []string
	MaxForwards int64
	Contact     string
	To          string
	ToRep       string
	// ToTag       string
	From string
	// FromTag     string
	RecordRoute string
	RequestURI  string
	CallID      string
	UserAgent   string
	Allow       string
	Supported   string
	Cseq        string

	ContentLength int64
	ContentType   string

	ResType string

	XAICall string
	Media   string

	// direction bool

	Content *SDP

	SrcAddr *icsnet.IcsNetAddr
	DstAddr *icsnet.IcsNetAddr
}

func NewSIP() (sip SIP) {
	return sip
}

func NewSDP() (sdp SDP) {
	return sdp
}

func NewSDPMedia() (sdpMedia SDPMedia) {
	return sdpMedia
}

var sdpMediaType = []string{"m", "a"}

func findSdpMedia(mediaType string) bool {
	for _, value := range sdpMediaType {
		if value == mediaType {
			return true
		}
	}

	return false
}

func (s *SIP) SipParser(byteArr []byte) (icserr *icserror.IcsError) {
	strSipLine := strings.Split(string(byteArr), "\r\n")

	isParserSIP := true

	sdp := NewSDP()
	sdpMedia := SDPMedia{}
	sdpMedias := []SDPMedia{}

	for line := range strSipLine {
		//fmt.Println("strSipLine[line]", len(strings.TrimSpace(strSipLine[line])), isParserSIP, strings.TrimSpace(strSipLine[line]))
		if line == 0 {
			s.parseFirstLineHeader(strings.TrimSpace(strSipLine[line]))
		} else {
			if isParserSIP && len(strings.TrimSpace(strSipLine[line])) == 0 {
				isParserSIP = false
			} else if !isParserSIP && len(strings.TrimSpace(strSipLine[line])) == 0 {
				break
			} else if !isParserSIP { //parse SDP
				if len(strings.TrimSpace(strSipLine[line])) > 0 {
					if len(strings.TrimSpace(strSipLine[line])) == 0 {
						//fmt.Println("RRRR", line)
						return nil
					}

					splits := strings.SplitN(string(strings.TrimSpace(strSipLine[line])), "=", 2)

					if len(splits) < 2 {
						//fmt.Printf("RRRR22 strSipLine[line] : [%s]\n", strSipLine[line])
						//fmt.Printf("RRRR22 strSipLine len: %d, line len: %d, byteArr: {%s}, SipLine:##### {%v}\n",
						//len(strSipLine), line, string(byteArr), strSipLine)
						break
						//return nil
					}

					name := strings.TrimSpace(splits[0])
					value := strings.TrimSpace(splits[1])

					//fmt.Println("@@@@", name, value)
					if findSdpMedia(name) {
						sdpMedia.setSdpMediaStruct(name, value)
					} else {
						sdp.setSdpStruct(name, value)
					}
				}
			} else {
				s.parseSip(strings.TrimSpace(strSipLine[line]))
			}
		}
	}
	sdpMedias = append(sdpMedias, sdpMedia)
	if isExistSdpMedia(sdpMedias) {
		//fmt.Println("$$$$", sdpMedias)
		//fmt.Printf("MediaDescription: %s", sdpMedias[0].MediaDescription)
		sdp.Media = &sdpMedias
	}

	// fmt.Println("sdp.Version", sdp.Version)
	// fmt.Println("sdp.Owner", sdp.Owner)
	// fmt.Println("sdp.Session", sdp.Session)
	// fmt.Println("sdp.Connection", sdp.Connection)

	if isExistSdp(sdp) {
		s.Content = &sdp
	}
	return nil
}

func isExistSdpMedia(sdpMedias []SDPMedia) bool {
	for _, value := range sdpMedias {
		if len(value.MediaDescription) == 0 {
			return false
		}
		// TODO Media만 존재하고, attributes 가 없는 경우 처리 방법
		// for _, val := range value.Attributes {
		// 	fmt.Println(val)
		// }
	}
	return true
}

func isExistSdp(sdp SDP) bool {
	if sdp.Version == 0 && sdp.Owner == "" && sdp.Session == "" && sdp.Connection == "" {
		return false
	}
	return true
}

func (s *SIP) parseFirstLineHeader(strSipLine string) (icserr *icserror.IcsError) {
	// fmt.Println(">>>>>>>>>>>parseFirstLineHeader", strSipLine)
	splits := strings.SplitN(strSipLine, " ", 3)

	if len(splits) < 3 {
		return icserror.ICSERRSIPHeader
	}

	icsSipMethod, err := GetSipMethod(splits[0], splits[1])
	if icsSipMethod == ICS_SIP_METHOD_SIP20 {
		s.ResType = splits[1]
	}
	if err != nil {
		return icserror.ICSERRSIPHeader
	}
	s.Method = icsSipMethod
	s.Version = splits[2]
	s.Source = splits[1]

	return nil
}

func GetSipMethod(strMethod, strSource string) (icsSipMethod IcsSipMethod, icserr *icserror.IcsError) {
	for method := IcsSipMethod(0); method < ICS_SIP_METHOD_NOT_FOUND; method++ {
		switch strings.ToUpper(strMethod) {
		case "REGISTER":
			return ICS_SIP_METHOD_REGISTER, nil
		case "INVITE":
			return ICS_SIP_METHOD_INVITE, nil
		case "SIP/2.0":
			return ICS_SIP_METHOD_SIP20, nil
		case "PRACK":
			return ICS_SIP_METHOD_PRACK, nil
		case "ACK":
			return ICS_SIP_METHOD_ACK, nil
		case "BYE":
			return ICS_SIP_METHOD_BYE, nil
		case "REFER":
			return ICS_SIP_METHOD_REFER, nil
		case "NOTIFY":
			return ICS_SIP_METHOD_NOTIFY, nil
		case "CANCEL":
			return ICS_SIP_METHOD_CANCEL, nil
		case "INFO":
			return ICS_SIP_METHOD_INFO, nil
		case "OPTIONS":
			return ICS_SIP_METHOD_OPTIONS, nil
		case "SUBSCRIBE":
			return ICS_SIP_METHOD_SUBSCRIBE, nil
		case "PUBLISH":
			return ICS_SIP_METHOD_PUBLISH, nil
		case "UPDATE":
			return ICS_SIP_METHOD_UPDATE, nil
		}
	}
	return ICS_SIP_METHOD_NOT_FOUND, nil
}

func (s *SIP) parseSip(data string) (icserr *icserror.IcsError) {
	if len(data) == 0 {
		return nil
	}

	splits := strings.SplitN(string(data), ":", 2)

	if len(splits) < 2 {
		return nil
	}

	name := strings.TrimSpace(splits[0])
	value := strings.TrimSpace(splits[1])

	s.setSipStruct(name, value)
	return nil
}

func (s *SIP) setSipStruct(name string, value string) (icserr *icserror.IcsError) {
	switch strings.ToUpper(name) {
	case "VIA":
		s.Via = append(s.Via, value)
		return nil
	case "MAX-FORWARDS":
		numValue, err := strconv.ParseInt(value, 10, 64)
		if err != nil {
			return icserror.ICSERRSIPHeader
		}
		s.MaxForwards = numValue
		return nil
	case "FROM":
		s.From = value

		// splits := strings.SplitN(string(value), "tag=", 2)

		// if len(splits) < 2 {
		// 	s.FromTag = ""
		// } else {
		// 	s.FromTag = strings.TrimSpace(splits[1])
		// }

		return nil
	case "TO":
		s.To = value
		return nil
	case "CALL-ID":
		s.CallID = value
		return nil
	case "CSEQ":
		// splits := strings.SplitN(string(value), " ", 2)
		// numValue, err := strconv.ParseInt(strings.TrimSpace(splits[0]), 10, 64)
		// if err != nil {
		// 	return icserror.ICSERRSIPHeader
		// }
		// s.Cseq = numValue
		s.Cseq = strings.ToUpper(value)
		return nil
	case "CONTACT":
		s.Contact = value
		return nil
	case "SUPPORTED":
		s.Supported = value
		return nil
	case "ALLOW":
		s.Allow = value
		return nil
	case "USER-AGENT":
		s.UserAgent = value
		return nil
	case "CONTENT-TYPE":
		s.ContentType = value
		return nil
	case "CONTENT-LENGTH":
		numValue, err := strconv.ParseInt(value, 10, 64)
		if err != nil {
			return icserror.ICSERRSIPHeader
		}
		s.ContentLength = numValue
		return nil
	case "X-AICALL":
		s.XAICall = value
	case "MEDIA":
		s.Media = value
	default:
		values := strings.Split(value, ";")

		// 헤더 값에 값을 없을 경우 초기화.. map이라서??
		if len(s.Headers) == 0 {
			s.Headers = map[string][]string{}
		}
		for _, fieldValue := range values {
			s.Headers[name] = append(s.Headers[name], strings.TrimSpace(fieldValue))
		}
		//fmt.Println("SIP~~~~~~~~~~~~~~~~~whoAU~~~~~~~~~~~~~~~~~" + value)
	}
	return nil
}

// representative
func (s *SIP) SetTo(name string) {
	name1 := strings.SplitN(s.To, "@", 2)
	tmp := "<sip:%s@" + name1[1]
	s.To = fmt.Sprintf(tmp, name)
}

// representative
func (s *SIP) SetToRep() {
	name1 := strings.SplitN(s.To, "@", 2)
	name2 := strings.SplitN(name1[0], ":", 1)

	s.ToRep = name2[0] + "@" + name1[1]
}

//implm interface Packeter
func (s *SIP) GetPacketData(addr []*icsnet.IcsNetAddr, packet []byte) *icserror.IcsError {
	s.SrcAddr = addr[0]
	s.DstAddr = addr[1]
	return s.SipParser(packet)

	//fmt.Println("SIP GetPacketData()", s.SrcAddr, s.DstAddr)
}

func (s *SIP) GetCallID() string {
	return s.CallID
}

func (s *SIP) GetURI() string {
	if s.Method != ICS_SIP_METHOD_SIP20 {
		uris := strings.SplitN(s.Source, ";", 2)
		if len(uris[0]) > 0 {
			agenturis := strings.SplitN(uris[0], ":", 3)
			//fmt.Println(">>>", uris, s.Method)
			return agenturis[1]
		}
	}

	return ""
}
