package main

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"os"
	"time"
)

//openSession is the biggest description for a user's openID
// | it contains a name called procedure, whose data is stored elsewhere
// |
// +-- a Procedure name is a alphanumerical string lessthan 128 bytes.
//     |
//     +--- state is part of procedure, a procedure consists of sequence of states

//where to find session data
var sessionDir = "sessions"

//where to find the procedure state information
var procedureDir = "procedure"

//openIDSessionData  openID user's session
type openIDSessionData struct {
	OpenID    string            `json:"OpenID"`    //who's session this is belongs to
	Procedure string            `json:"Procedure"` //name of the current current process, alphanumerical
	CreateAt  int32             `json:"CreateAt"`  //when is this session created
	UpdateAt  int32             `json:"UpdateAt"`  //when is this session updated
	Expire    int32             `json:"Expire"`    //unix timestamp of when this Procedure expires
	KvPair    map[string]string `json:"KvPair"`    //key value pair persistant for this session
	//
	//
	//current state for the prcedure
	state chatState
}

func createEmptySession(openID string, expire int32) (result openIDSessionData) {
	result.OpenID = openID
	result.Procedure = ""
	now := int32(time.Now().Unix())
	result.CreateAt = now
	result.UpdateAt = now
	result.Expire = now + expire
	result.KvPair = map[string]string{}
	return
}

func (ss *openIDSessionData) writeSession() (err error) {
	openID := ss.OpenID
	path := getSessionPath(openID)
	r, err := json.Marshal(ss)
	if err == nil {
		err = ioutil.WriteFile(path, r, 0600)
		if err != nil {
			log.Printf("Error writing session %s: \r\n %s \r\n", openID, r)
			log.Println(err)
		} else {
			log.Println("write session to " + path)
			//save state
			err = ss.state.Save()
			if err != nil {
				log.Printf("Error: cannot write session data %s", openID)
				log.Println(err)
			}
		}
	} else {
		log.Printf("Error encoding session %s ", openID)
		log.Println(err)
	}
	return
}

func isExpired(timestamp int32) bool {
	now := int32(time.Now().Unix())
	return now > timestamp
}

func getSessionPath(openID string) (path string) {
	path = sessionDir + string(os.PathSeparator) + openID + ".json"
	ensurePathExist(path)
	return path
}

func (ss *openIDSessionData) Delete() {
	path := getSessionPath(ss.OpenID)
	if isFileExist(path) {
		err := os.Remove(path)
		if err != nil {
			log.Println(err)
		}
	}
}

func (ss *openIDSessionData) Save() {
	//invalid procedure
	if isExpired(ss.Expire) || ss.Procedure == "" {
		ss.Delete()
	} else {
		ss.writeSession()
	}
}

//read sessions/openID.json to check whether its a Procedure for ongoing dialog
func (ss *openIDSessionData) Load(openID string) (result openIDSessionData, err error) {
	result = createEmptySession(openID, 3600)
	path := getSessionPath(openID)
	if isFileExist(path) {
		log.Printf("read session from %s\r\n", path)
		body, err := ioutil.ReadFile(path)
		if err != nil { //read file error
			log.Println("Error session reading " + path)
		} else {
			err = json.Unmarshal(body, &result)
			if err != nil {
				log.Printf("Session Content [path=%s] not correct: ", path)
				result = createEmptySession(openID, 3600)
			} else { //load procedure state if any
				if !isExpired(result.Expire) {
					procedure := result.Procedure
					result.state, err = getCurrentState(openID, procedure)
				} else {
					result = createEmptySession(openID, 3600)
				}
			}
		}
	}
	*ss = result
	return
}

func (ss *openIDSessionData) setProcedure(procedure string) {
	ss.Procedure = procedure
	ss.UpdateAt = int32(time.Now().Unix())
}

func (ss *openIDSessionData) refreshExpire(seconds int32) {
	ss.UpdateAt = int32(time.Now().Unix())
	ss.Expire = ss.UpdateAt + seconds
}

func (ss *openIDSessionData) setKvPair(key, val string) {
	ss.KvPair[key] = val
	ss.UpdateAt = int32(time.Now().Unix())
}

func (ss *openIDSessionData) getVal(key string) string {
	return ss.KvPair[key]
}

//main entry point for processing each incoming message
//this stage has session date available
func (ss *openIDSessionData) incomingMsg(v InWechatMsg) {
	openID := v.header.FromUserName
	//kfSendTxtAs(openID, "信息收到"+v.header.MsgType, "孙鹏")
	//are we in an existing procedure
	proc, found := AllProc[ss.Procedure]
	if found {
		proc.serve(ss, v) //transit to new state
	} else {
		processed := ss.serveCommand(v) //menu or txt command e.g. search
		if !processed {                 // transfer to Customer Service (kf)
			//start transfer
			ss.state.response, _ = BuildKFTransferAnyOneMsg(openID)
			v.replyXML(ss.state.response)
			kfSendTxt(openID, "已转接校友会理事会，稍后答复您")
		}
	}
	return
}
