package icssessionmanager

import (
	"runtime"
	"runtime/debug"
	"strconv"
	"strings"
	"time"

	"gitlab.com/cinnamon/voiceagent/icsconf"
	"gitlab.com/cinnamon/voiceagent/icserror"
	"gitlab.com/cinnamon/voiceagent/icshttpclient"
	"gitlab.com/cinnamon/voiceagent/icslog"
	"gitlab.com/cinnamon/voiceagent/icsnet"
	"gitlab.com/cinnamon/voiceagent/icspacketparser"
	"gitlab.com/cinnamon/voiceagent/recorddata/readcallsignal"
	"gitlab.com/cinnamon/voiceagent/recorddata/responsecs"
)

//////////////////////////////////////////////////////////////////
//session operator
func findSessionWithCallID(callId string) *IcsSession {
	sessions := getSessionInstance()
	for _, session := range sessions {
		session.m.Lock()
		if strings.Compare(session.callId, callId) == 0 {
			session.m.Unlock()
			return session
		}
		session.m.Unlock()
	}

	return nil
}

func findSessionWithStation(station string) *IcsSession {
	sessions := getSessionInstance()
	for _, session := range sessions {
		session.m.Lock()
		//fmt.Printf("findsessionWithStation: %s, %s\n", session.Station, station)
		if strings.Compare(session.Station, station) == 0 {
			session.m.Unlock()
			return session
		}
		session.m.Unlock()
	}

	return nil
}

func findSessionWithBotToken(token string) *IcsSession {
	sessions := getSessionInstance()
	for _, session := range sessions {
		session.m.Lock()
		//fmt.Printf("findsessionWithStation: %s, %s\n", session.Station, station)
		if strings.Compare(session.botToken, token) == 0 {
			session.m.Unlock()
			return session
		}
		session.m.Unlock()
	}

	return nil
}

func findSessionWithRTPPort(srcPort int, dstPort int) *IcsSession {
	sessions := getSessionInstance()
	for _, session := range sessions {
		session.m.Lock()
		if (session.srcPort == srcPort && session.dstPort == dstPort) || (session.srcPort == dstPort && session.dstPort == srcPort) {
			session.m.Unlock()
			return session
		}
		session.m.Unlock()
	}

	return nil
}

/* 미사용 코드로 임시 주석 처리 ( csma 210512 )
func FindSessionID(sip icspacketparser.SIP) (IcsSessionID, *icserror.IcsError) {
	callID := sip.GetCallID()
	if len(callID) > 0 {
		session := findSessionWithCallID(callID)
		if session != nil {
			return IcsSessionID(session.ID), nil
		}
	}

	return IcsSessionID(-1), icserror.ICSERRSESSNotFoundSession
}
*/

func FindSession(data interface{}) (*IcsSession, *icserror.IcsError) {
	//func FindSession(sip icspacketparser.SIP) (*IcsSession, *icserror.IcsError) {
	switch v := data.(type) {
	case icspacketparser.SIP:
		sip := v
		callID := sip.GetCallID()

		/*
			if sip.Method == icspacketparser.ICS_SIP_METHOD_BYE || sip.Method == icspacketparser.ICS_SIP_METHOD_CANCEL {
				//fmt.Println(">>>>>>>>>>>>>>>>>>>>>>>", callID)
			}
		*/

		if len(callID) > 0 {
			session := findSessionWithCallID(callID)
			if session != nil {
				return session, nil
			}
		}
	case icspacketparser.RTP:
		rtp := v
		srcPort := rtp.SrcAddr.Port
		dstPort := rtp.DstAddr.Port
		session := findSessionWithRTPPort(srcPort, dstPort)
		if session != nil {
			return session, nil
		}
	case *readcallsignal.CallSignal:
		//case icspacketparser.CallSignal:
		cs := v
		session := findSessionWithStation(cs.Station)
		//fmt.Println("FindSession cs!", cs.Station, cs, session)
		if session != nil {
			return session, nil
		}
		//default:
		//fmt.Printf(">>>>>>>>>>>>>>>>>>%v-----%T\n", v, v)
	}

	return nil, icserror.ICSERRSESSNotFoundSession
}

/* 미사용 코드로 임시 주석 처리 ( csma 200512 )
func AllocSessionID(callId string, direction bool) (IcsSessionID, *icserror.IcsError) {
	if len(callId) == 0 {
		return IcsSessionID(-1), icserror.ICSERRInvalidParam
	}

	sessions := getSessionInstance()

	var id int
	for _, session := range sessions {
		id++
		if len(session.callId) == 0 {
			session.callId = callId
			session.direction = direction
			return IcsSessionID(session.ID), nil
		}
	}

	if id >= channelNum {
		return IcsSessionID(-1), icserror.ICSERRSESSMaxSessionNumber
	}

	return IcsSessionID(-1), icserror.ICSERRSESSNotFoundSession
}
*/

func goid() int {
	var buf [64]byte
	n := runtime.Stack(buf[:], false)
	idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0]
	id, err := strconv.Atoi(idField)
	if err != nil {
		return -1
	}
	return id
}

func AllocSession(channelID string, serverID string, station string, callId string, direction bool) (*IcsSession, *icserror.IcsError) {
	/*
		if len(callId) == 0 {
			return nil, icserror.ICSERRInvalidParam
		}
	*/

	l := icslog.GetIcsLog()
	sessions := getSessionInstance()

	var id int = 0
	for _, session := range sessions {
		id++
		session.m.Lock()
		if len(session.Station) == 0 {
			//if len(session.callId) == 0 || len(session.Station) == 0 {
			session.ChannelID = channelID
			session.ServerID = serverID
			session.Station = station
			session.callId = callId
			session.direction = direction
			session.txSeq = -1
			session.rxSeq = -1
			session.isSTTStart = false

			/*/////////////////
			fname := fmt.Sprintf("./rx_%03d.voice.g729", id)
			session.rxFile, _ = os.OpenFile(fname, os.O_CREATE|os.O_WRONLY, 0644)
			fname = fmt.Sprintf("./tx_%03d.voice.g729", id)
			session.txFile, _ = os.OpenFile(fname, os.O_CREATE|os.O_WRONLY, 0644)
			*/ /////////////////

			session.m.Unlock()
			l.Printf(icslog.LOG_LEVEL_INFO, session.ID, "Session[%d] Allocated(%d). Station: %s ==========================",
				session.ID, session.goroutineID, session.Station)
			return session, nil
		}
		session.m.Unlock()
	}

	if id >= channelNum {
		return nil, icserror.ICSERRSESSMaxSessionNumber
	}

	return nil, icserror.ICSERRSESSNotFoundSession
}

func (s *IcsSession) RemoveSession() *icserror.IcsError {
	l := icslog.GetIcsLog()
	defer func() {
		if err := recover(); err != nil {
			switch v := err.(type) {
			case error:
				icserror.ICSERRNETNotConnectError.SetError(v)
				l.Printf(icslog.LOG_LEVEL_WARN, s.ID, "PANIC! %s\n%s", icserror.ICSERRNETNotConnectError.GetError().Error(), string(debug.Stack()))
			default:
				l.Print(icslog.LOG_LEVEL_WARN, s.ID, icserror.ICSERRNETNotConnectError.GetMessage())
			}
		}
	}()

	//s.VoiceNeter.Close()
	//s.CallSignalNeter.Close()

	//id := s.ID
	//s = &IcsSession{ID: id}

	//s.rxFile.Close()
	//s.txFile.Close()

	e := &IcsSession{}
	s.m.Lock()
	s.eventSystem.ClearJob()
	s.callId = e.callId
	s.Station = e.Station
	s.srcPort = e.srcPort
	s.dstPort = e.dstPort
	s.direction = e.direction
	s.isStart = 0
	s.dtmf = ""
	//bot
	s.url = e.url
	s.custid = e.custid
	s.sstation = e.sstation

	s.isFoundPayload = false
	if s.RxConverter != nil {
		s.RxConverter.Close()
		s.RxConverter = nil
	}
	if s.TxConverter != nil {
		s.TxConverter.Close()
		s.TxConverter = nil
	}

	s.botStatus = nil
	s.botToken = ""
	s.isSTTStart = false

	s.txSeq = -1
	s.rxSeq = -1
	s.MethodAutomata = 0
	s.m.Unlock()

	l.Printf(icslog.LOG_LEVEL_INFO, s.ID, "Removed Session[%d] ==========================", s.goroutineID)

	return nil
}

func GetSession(sessionID IcsSessionID) (*IcsSession, *icserror.IcsError) {
	if sessionID < IcsSessionID(0) {
		return nil, icserror.ICSERRInvalidParam
	}

	sessions := getSessionInstance()
	return sessions[int(sessionID)], nil
}

func (s IcsSession) GetSessionID() IcsSessionID {
	return IcsSessionID(s.ID)
}

func (s *IcsSession) CheckSignalMethod(sip icspacketparser.SIP) *icserror.IcsError {
	if s.CheckAutomata(&sip) {
		if s.MethodAutomata != ICS_SIP_AUTOMATA_CANCEL {
			s.SetSessionMethod(sip)
		}
	} else {
		//fmt.Println("^^^^^", "unMatched Automata", s.MethodAutomata, sip.Method)
	}

	if sip.Method == icspacketparser.ICS_SIP_METHOD_SIP20 && strings.Contains(sip.Cseq, " BYE") || s.MethodAutomata == ICS_SIP_AUTOMATA_CANCEL && sip.Method == icspacketparser.ICS_SIP_METHOD_ACK {
		//fmt.Println("^^^^^", "call s.RemoveSession() sip.Method, s.MethodAutomata ", sip.Method, s.MethodAutomata)
		serr := s.RemoveSession()
		if serr != nil {
			//serr.Print()
			return serr
		}
	}

	return nil
}

//////////////////////////////////////////////////////////////////
//
func (s *IcsSession) SetTimestamp(now time.Time) {
	s.lastTimestamp = now
}

func (s IcsSession) GetPayloadType() icspacketparser.PayloadType {
	return s.payloadType
}

func (s *IcsSession) GetDirection() bool {
	return s.direction
}

func (s IcsSession) GetCallID() string {
	return s.callId
}

func (s IcsSession) GetBotToken() string {
	return s.botToken
}

func (s *IcsSession) SetAgentConfig(agentconf *icsconf.AgentConfig) {
	s.AgentConfig = agentconf
}

func (s *IcsSession) FindAgentConfig(agentID string) *icsconf.AgentConfig {
	conf := icsconf.GetIcsConfig()

	//a := strings.SplitN(agentID, string(0), 2)
	for _, agentconf := range conf.AgentConfig {
		//fmt.Printf("308LINE iter: %d, [%v] [%v] [%v]\n", iter, []byte(agentconf.Name), []byte(agentID), []byte(a[0]))
		if strings.Contains(agentID, agentconf.Name) {
			//fmt.Println(">>>>", iter, agentconf.Name)
			return &agentconf
		}
	}
	return nil
}

func (s *IcsSession) SendVoiceGatewayByeSignal() {
	l := icslog.GetIcsLog()
	conf := icsconf.GetIcsConfig()
	agentname := s.AgentConfig.Name
	telno := s.botStatus.Data.TelNo
	l.Printf(icslog.LOG_LEVEL_DEBUG2, s.ID, "###### Start BYE Message!!!!!!!!!")
	r := responsecs.NewResponse(agentname, telno, responsecs.BYE_COMMAND, responsecs.RC_SUCCESS, 0)
	addr := icsnet.NewNetAddrWithIPPort(conf.VGWCommandConfig.IP, conf.VGWCommandConfig.Port)
	wlen, werr := icsnet.SendByeSignal(&addr, nil, r.GetData())
	if werr != nil {
		l.Printf(icslog.LOG_LEVEL_ERROR, s.ID, "Failed to send BYE to Voice Gateway - %s [%+v]",
			werr.GetError(), s.TcpConn)
	}
	l.Printf(icslog.LOG_LEVEL_DEBUG2, s.ID, "Sent BYE message to VoiceGateway: %d", wlen)

	// 윗쪽에서 코드를 봐야함!!
	s.Stop()
	serr := s.RemoveSession() // 세션을 날려야 바로 전화를 걸었을때 새로 받아짐
	if serr != nil {
		l.Printf(icslog.LOG_LEVEL_ERROR, s.ID, "RemoveSession Error %s", serr.GetMessage())
	}
}

func (s *IcsSession) SendVoiceGatewayBotByeSignal(url string, custid string, sstation string) { // Send Bot HANGUP Message
	l := icslog.GetIcsLog()
	conf := icsconf.GetIcsConfig()
	agentname := s.AgentConfig.Name
	telno := s.botStatus.Data.TelNo
	l.Printf(icslog.LOG_LEVEL_DEBUG2, s.ID, "###### Start BYE Message!!!!!!!!!")
	r := responsecs.NewResponse(agentname, telno, responsecs.BYE_COMMAND, responsecs.RC_SUCCESS, 0)
	addr := icsnet.NewNetAddrWithIPPort(conf.VGWCommandConfig.IP, conf.VGWCommandConfig.Port)
	wlen, werr := icsnet.SendByeSignal(&addr, nil, r.GetData())
	if werr != nil {
		l.Printf(icslog.LOG_LEVEL_ERROR, s.ID, "Failed to send BYE to Voice Gateway - %s [%+v]",
			werr.GetError(), s.TcpConn)
	}
	l.Printf(icslog.LOG_LEVEL_DEBUG2, s.ID, "Sent BYE message to VoiceGateway and Bot: %d", wlen)

	processResp := icshttpclient.PostProcess(url, "HANGUP", "", custid, "ICS_RCP", s.callId, sstation, s.botToken, "")
	if processResp == nil {
		l.Print(icslog.LOG_LEVEL_ERROR, s.ID, "Response Error")
	}

	//close voiceneter
	verr := s.VoiceNeter.Close()
	if verr != nil {
		l.Printf(icslog.LOG_LEVEL_ERROR, s.ID, "Failed to close Voice port - %s", verr.GetError())
	} else {
		l.Print(icslog.LOG_LEVEL_INFO, s.ID, "Closed Voice port")
	}

	//close session
	s.Stop()
	serr := s.RemoveSession()
	if serr != nil {
		l.Printf(icslog.LOG_LEVEL_ERROR, s.ID, "RemoveSession Error %s", serr.GetMessage())
	}
}

//////////////////////////////////////////
//DTMF
// add DTMF
func (s *IcsSession) AddDtmf() {
	inputDtmf := s.cs.InOut
	if inputDtmf == "10" {
		inputDtmf = "#"
	} else if inputDtmf == "11" {
		inputDtmf = "*"
	}

	s.m.Lock()
	s.dtmf += inputDtmf
	s.m.Unlock()
}

// reset DTMF
func (s *IcsSession) ResetDtmf() {
	s.m.Lock()
	s.dtmf = ""
	s.m.Unlock()
}

// get DTMF
func (s *IcsSession) GetDtmf(dtmfEnd string) string {
	result := ""
	if s.botStatus.Data.MaxDigit == len(s.dtmf) || strings.Contains(s.dtmf, dtmfEnd) { // len check
		result = s.dtmf
	} else if strings.Contains(s.dtmf, "*") {
		result = s.custid
	}
	return result
}
