package modules

import (
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"net/rpc"
	"os"
	"os/signal"
	"strconv"
	"syscall"
	"time"
)

type Moscato struct {
	queue               MsgQueue // Queue which store the message sent by Microservice
	SendQueue           chan myType // Queue which store the message which send to Microservice
	MicroServiceManager NodeManager `inject:""`
	MatchingManager     match_manager //
	SubscriptionManager sub_manager
	SecureManager       SecurityManager `inject:""`
}

type myType struct {
	subList []string // Subscriber List registered in Middleware
	pubMsg  MsgUnit
	err     error
}

type Reply struct { // Return value of RPC call
	CompleteLog string // Log whether received or not
}

type Receiver struct { //Variable for register in RPC server
	moscato *Moscato
}

type Args struct { // Parameter for RPC call
	JsonMsg []byte
	Kind    int
}

/*
MicroService(MS) -----> Message Middleware(MM)
- When MiddleWare executed, MiddleWare Server is opened(Microservice automatically will be Client)(Port number is 8150)
1. Call Send2MM(Send to MessageMiddleware)
2. Transfer Message(Json type) to Message Middleware with RPC call which call MmReceive function(receive message from microservice)
3. After chek message type in MmReceive, send to MM's Receive function
4. In MM's Receive, deal with message based on message type
*/

func (receiver Receiver) MmReceive(args Args, reply *Reply) error { //
	// Unmarshal Json message based on message type
	switch args.Kind {

	case KSM: // Key Share Message
		var msg KeyShareMsg
		err := json.Unmarshal(args.JsonMsg, &msg)
		if err != nil {
			return err
		}
		go func() {
			_, err := receiver.moscato.Receive(msg)
			if err != nil {

			}
		}()
		reply.CompleteLog = "KSM received"
	case PM: //Publish Message
		var msg PublishMsg
		err := json.Unmarshal(args.JsonMsg, &msg)
		if err != nil {
			return err
		}

		go func() {
			_, err := receiver.moscato.Receive(msg)
			if err != nil {

			}
		}()
		reply.CompleteLog = "PM received"
	case SM: // Subscription Message
		var msg SubscriptionMsg
		err := json.Unmarshal(args.JsonMsg, &msg)
		if err != nil {
			return err
		}
		go func() {
			_, err := receiver.moscato.Receive(msg)
			if err != nil {

			}
		}()
		reply.CompleteLog = "SM received"
	case RM: // Register Message
		var msg RegisterMsg
		err := json.Unmarshal(args.JsonMsg, &msg)
		if err != nil {
			return err
		}
		go func() {
			_, err := receiver.moscato.Receive(msg)
			if err != nil {
				fmt.Println(err)
			}
		}()
		reply.CompleteLog = "RM received"
	case WM: // Withdraw Message
		var msg WithdrawMsg
		err := json.Unmarshal(args.JsonMsg, &msg)
		if err != nil {
			return err
		}
		go func() {
			_, err := receiver.moscato.Receive(msg)
			if err != nil {

			}
		}()
		reply.CompleteLog = "WM received"
	default: // Not defined message type
		return errors.New("message type Error: Not registered message type")
	}
	//reply.CompleteLog = "received completely"
	return nil
}

/*
Receive function(Middleware)
- function that push message to Message Queue from RPC
- parameter is message unit(MsgUnit)
1. Middleware receives message from Microservice
2. After process message with preProcessMsg function, push it to queue
 */
func (moscato *Moscato) Receive(msg MsgUnit) (MsgUnit, error) {
	logger := NewMyLogger()
	defer logger.Sync()

	var msg_type = msg.CheckType() // Check message type

	switch msg_type {

	case KSM: //Key share msg

	case PM: //Publish msg
		fromNodeName, _ := moscato.MicroServiceManager.GetIpaddr(msg.(PublishMsg).From)
		logger.Info("PM received from:[" + fromNodeName + "]")
		moscato.queue.push(moscato.preProcessMsg(msg))

	case SM: //Subscription msg
		fromNodeName, _ := moscato.MicroServiceManager.GetIpaddr(msg.(SubscriptionMsg).From)
		logger.Info("SM received from:[" + fromNodeName + "]")
		err := moscato.SubscriptionManager.addSubscription(moscato.preProcessMsg(msg))
		if err != nil {
			logger.Warn(err.Error())
		}

	case RM: //Register msg(register microservice)
		var newMsg RegisterMsg
		newMsg = msg.(RegisterMsg)
		logger.Info("RM received from:[" + newMsg.From + "]")

		newNode := MSNode{newMsg.From, newMsg.From}
		resultAddNode := moscato.MicroServiceManager.AddMicroservice(newNode)
		if resultAddNode {
			logger.Info("Node added successful")
		} else {
			logger.Error("Node is already added, ignore RM")
			return msg, nil
		}

		addr, _ := moscato.MicroServiceManager.GetIpaddr(newMsg.From)

		moscato.SecureManager.RegKey(newMsg)
		logger.Debug("Registered microservice: address " + addr +
			" / key " + strconv.FormatUint(uint64(moscato.SecureManager.GetNodeKey(newMsg.From)), 10))

		// send ackRM message
		go moscato.Send2MS(addr, newMsg)

	case WM: //Withdraw msg(remove node)
		fromNodeName, _ := moscato.MicroServiceManager.GetIpaddr(msg.(WithdrawMsg).From)
		logger.Info("WM received from:[" + fromNodeName + "]")
		//ip := msg.(WithdrawMsg).From
		//sublist := moscato.SubscriptionManager.ip2sub[ip]
		//fmt.Println("prev list = ", sublist)
		moscato.MicroServiceManager.RemoveMicroservice(msg.(WithdrawMsg).From)
		moscato.SecureManager.RemoveSecureKey(msg.(WithdrawMsg).From)
		//moscato.SubscriptionManager.delete(ip)
		//sublist2 := moscato.SubscriptionManager.ip2sub[ip]
		//fmt.Println("after list =", sublist2)

	default:
		return nil, errors.New("Message type Error: Not registered message type")
	}

	return msg, nil
}

/*
Message Middleware(MM) ----> Microservice(MS)
- When Microservice executed, Microservice Server is opened(Message Middleware automatically will be Client)(Port number is 8160)
1. Call Send2MS(Send to Microservice)
2. Transfer Message(Json type) to Microservice with RPC call which call MsReceive function(receive message from message middleware)
3. After chek message type in MsReceive, send to MS's Receive function
4. In MS's Receive, deal with message based on message type
*/

func (moscato *Moscato) Send2MS(ipaddress string, msg MsgUnit) {
	logger := NewMyLogger()
	defer logger.Sync()

	client, err := rpc.Dial("tcp", ipaddress+":8150")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer client.Close()

	reply := new(Reply)
	jmsg, _ := msg.ConvertToJson()
	args := Args{
		JsonMsg: jmsg,
		Kind:    msg.CheckType(),
	}
	err = client.Call("Receiver.Receive", args, reply)
	if err != nil {
		fmt.Println(err)
		return
	}
	//log.Println(reply.CompleteLog) //잘 받았는지 확인 해줌
	// 마이크로 서비스에게 받은 메시지는 노란색으로 출력
	//log.Println(reply.CompleteLog)
	logger.Debug(reply.CompleteLog)
}

/*
-- preProcessMsg() --
- Message Encypted form is message + K(shared by clients and hidden from the brokers) + 𝜑k(private keys for publishers and subscribers)
- This function substract 𝜑k from message + K + 𝜑k
- For matching easily

 */
func (moscato *Moscato) preProcessMsg(originalMsg MsgUnit) MsgUnit {
	if originalMsg.CheckType() == PM {
		pubMsg := originalMsg.(PublishMsg)
		for index := 0; index < len(pubMsg.Topic); index++ {
			pubMsg.Topic[index] = pubMsg.Topic[index] - moscato.SecureManager.GetNodeKey(pubMsg.From)
		}
		for index := 0; index < len(pubMsg.Value); index++ {
			pubMsg.Value[index] = pubMsg.Value[index] - moscato.SecureManager.GetNodeKey(pubMsg.From)
		}
		return pubMsg
	} else if originalMsg.CheckType() == SM {
		subMsg := originalMsg.(SubscriptionMsg)
		for index := 0; index < len(subMsg.Topic); index++ {
			subMsg.Topic[index] = subMsg.Topic[index] - moscato.SecureManager.GetNodeKey(subMsg.From)
		}
		for index := 0; index < len(subMsg.Value); index++ {
			subMsg.Value[index] = subMsg.Value[index] - moscato.SecureManager.GetNodeKey(subMsg.From)
		}
		return subMsg
	}
	return nil
}

/*
-- SendWithEncrypt() --
1. pull messages that send to Middleware from SendQueue
2. send messages with ReEncrypt while checking subList(Subscriber List)
 */
func (moscato *Moscato) SendWithEncrypt() MsgUnit {
	for {
		mt := <-moscato.SendQueue
		if mt.err == nil {
			for index := 0; index < len(mt.subList); index++ {
				tmpNode := mt.subList[index]
				tmpNodeIpAddr, _ := moscato.MicroServiceManager.GetIpaddr(tmpNode)
				//moscato.SecureManager.ReEncPubMsg(mt.pubMsg.(PublishMsg), tmpNode)
				//fmt.Println("publish: ", mt.pubMsg)
				//moscato.Send2MS(tmpNodeIpAddr, mt.pubMsg)
				moscato.Send2MS(tmpNodeIpAddr, moscato.SecureManager.ReEncPubMsg(mt.pubMsg.(PublishMsg), tmpNode))
			}
		}
		return nil
	}
}

func (moscato *Moscato) Run() {
	logger := NewMyLogger()
	defer logger.Sync()

	config := AppConfig{moscato}
	config.config()

	sigs := make(chan os.Signal, 1)
	done := make(chan bool, 1)

	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

	go func() {
		sig := <-sigs
		//withDraw(message, client)
		//fmt.Println(sig)
		_ = sig
		done <- true
		logger.Info("terminate Moscato Message Middleware")
		os.Exit(0)
	}()

	//Initialize Moscato Struct variable
	receiver := Receiver{moscato: moscato} // variable defintion for RPC call
	err := moscato.queue.queue_init()
	moscato.SendQueue = make(chan myType)
	moscato.SubscriptionManager.Initialize()

	if err != nil {
		fmt.Println(err)
		return
	}

	send2MSFile, _ := os.Create("./send2MsTime.log")
	defer send2MSFile.Close()
	//go routine -> matching 동작
	go func() {
		for {
			msg := moscato.queue.pop(true)
			encStartTime := time.Now()
			go moscato.Matching(msg)
			encElapsedTime := time.Since(encStartTime)
			go moscato.SendWithEncrypt()
			fmt.Printf("MM enc시간: %d\n\n", encElapsedTime.Nanoseconds())
			fmt.Fprintln(send2MSFile, encElapsedTime.Nanoseconds())
		}
	}()

	//go moscato.CheckQueue()

	//rpc 등록 -> Receive 함수
	//Registr RPC, Receive function
	err = rpc.Register(receiver)
	if err != nil {
		println(err)
		return
	}

	go Listen()
	logger.Info("initializing complete")

	<-done
}

func Listen() {
	logger := NewMyLogger()
	defer logger.Sync()

	l, err1 := net.Listen("tcp", ":8160")

	if err1 != nil {
		logger.Fatal("Unable to listen on given port: " + err1.Error())
	}
	defer l.Close()

	for {
		conn, _ := l.Accept()
		go rpc.ServeConn(conn)
	}
}
