// stratum.go
package stratum

import (
	"bufio"
	"math/rand"

	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"strconv"

	"bytes"

	"net"
	"pool/internal/cache"
	"pool/internal/db"
	"pool/internal/msg"
	"pool/internal/server/coin"
	"pool/internal/utility"
	"regexp"
	"strings"
	"time"

	"github.com/rs/zerolog"
	"gopkg.in/natefinch/lumberjack.v2"

	"go.uber.org/zap"
)

const STRATUM_PING_INTERVAL_CNT int = 3
const STRATUM_PING_FAILED_MAX_CNT int = STRATUM_PING_INTERVAL_CNT * 4

// Exception Macro
const MINER_ERR_UNKNOWN int = 20
const MINER_ERR_NOT_FOUND_JOB int = 21
const MINER_ERR_DUP_SHARE int = 22
const MINER_ERR_LOW_DIF_SHARE int = 23
const MINER_ERR_UNAUTH_WORKER int = 24
const MINER_ERR_NOT_SUBSCRIBED int = 25
const MINER_ERR_ILLEGAL_METHOD int = 26
const MINER_ERR_ILLEGAL_PARARMS int = 27
const MINER_ERR_IP_BANNED int = 28
const MINER_ERR_INVALID_USERNAME int = 29
const MINER_ERR_INTERNAL_ERROR int = 30
const MINER_ERR_TIME_TOO_OLD int = 31
const MINER_ERR_TIME_TOO_NEW int = 32
const MINER_ERR_ILLEGAL_VERMASK int = 33
const MINER_ERR_STALED_JOB int = 34

type Exception_reply struct {
	ID     float64        `json:"id"`
	Result interface{}    `json:"result"`
	Error  [3]interface{} `json:"error"`
}

type Exception_reply_str struct {
	ID     string         `json:"id"`
	Result interface{}    `json:"result"`
	Error  [3]interface{} `json:"error"`
}

type Subscribe_reply struct {
	Result [3]interface{} `json:"result"`
	ID     float64        `json:"id"`
	Error  interface{}    `json:"error"`
}

type SubscribeGpu_reply struct {
	Jsonrpc string         `json:"jsonrpc"`
	Result  [3]interface{} `json:"result"`
	ID      float64        `json:"id"`
	Error   interface{}    `json:"error"`
}

type Subscribe_reply_str struct {
	Result [3]interface{} `json:"result"`
	ID     string         `json:"id"`
	Error  interface{}    `json:"error"`
}

type Notify_msg struct {
	ID     interface{}    `json:"id"`
	Method string         `json:"method"`
	Params [9]interface{} `json:"params"`
}

type Difficulty_msg struct {
	ID     interface{} `json:"id"`
	Method string      `json:"method"`
	Params [1]float64  `json:"params"`
}

type DifficultyNexa_msg struct {
	ID     interface{} `json:"id"`
	Method string      `json:"method"`
	Params [1]string   `json:"params"`
}

type DifficultyNexaGpu_msg struct {
	Jsonrpc string      `json:"jsonrpc"`
	Method  string      `json:"method"`
	Params  [1]float64  `json:"params"`
	ID      interface{} `json:"id"`
}

type ExtranonceSubscribeGpu_reply struct {
	ID      interface{} `json:"id"`
	Jsonrpc string      `json:"jsonrpc"`
	Result  bool        `json:"result"`
}

type Authorize_reply struct {
	Result bool        `json:"result"`
	ID     float64     `json:"id"`
	Error  interface{} `json:"error"`
}

type Authorize_reply_str struct {
	Result bool        `json:"result"`
	ID     string      `json:"id"`
	Error  interface{} `json:"error"`
}

type AlphSubmitParams struct {
	JobID      string `json:"jobId"`
	FromGroup  int    `json:"fromGroup"`
	ToGroup    int    `json:"toGroup"`
	Nonce      string `json:"nonce"`
	Worker     string `json:"worker"`     // user
	WorkerName string `json:"workerName"` // miner
}

type AlphSubmitNonce struct {
	Id     interface{}      `json:"id"`
	Method string           `json:"method"`
	Params AlphSubmitParams `json:"params"`
}

type AlphExtranonce struct {
	ID     interface{} `json:"id"`
	Method string      `json:"method"`
	Params []string    `json:"params"`
}

type Submit_nonce struct {
	ID     interface{} `json:"id"`
	Method string      `json:"method"`
	Params []string    `json:"params"`
}

type Reconnect_msg struct {
	ID     interface{} `json:"id"`
	Method string      `json:"method"`
	Params []string    `json:"params"`
}

type Ping_msg struct {
	ID     float64     `json:"id"`
	Method string      `json:"method"`
	Params interface{} `json:"params"`
}

type Authorize_msg struct {
	ID     interface{} `json:"id"`
	Method string      `json:"method"`
	Params []string    `json:"params"`
}

type SubmitHashRate_msg struct {
	Id         int      `json:"id"`
	Method     string   `json:"method"`
	Jsonrpc    string   `json:"jsonrpc"`
	Worker     string   `json:"worker"`
	WorkerName string   `json:"workerName"`
	Params     []string `json:"params"`
}

func Conn_tx(conn net.Conn, body []byte) error {
	_, err := conn.Write(body)
	if err != nil {
		conn.Close()
	}
	return err
}

func Conn_rx(reader *bufio.Reader) (line string) {
	line, err := reader.ReadString('\n')
	if err != nil {
		//if err != io.EOF {
		//logg.Error("[server]", zap.String("ReadString", err.Error()))
		return ""
		//}
	}
	strings.TrimSpace(line)
	return line
}

// miner-->server
func Handle_subscribe(miner *coin.MinerObj, id float64, extranonce1 string) {
	miner.TxLock.Lock()
	var results [1][2]string
	results[0][0] = "mining.notify"
	results[0][1] = miner.MinerId
	var result [3]interface{}
	result[0] = results
	//result[1] = miner.Job.Extranonce1
	/*be1 := make([]byte, 4)
	binary.LittleEndian.PutUint32(be1, (miner.Server.Extranonce1 + 0x81000000))
	result[1] = "0000000000000000" + hex.EncodeToString(be1)
	miner.Server.Extranonce1++*/
	result[1] = extranonce1
	//miner.Server.Logg.Debug("[server]", zap.Uint64("extra2", miner.Job.Extranonce2_size))
	if miner.Job.Extranonce2_size == 0 {
		result[2] = 4
	} else {
		result[2] = miner.Job.Extranonce2_size
	}
	var ack Subscribe_reply
	ack.ID = id
	ack.Result = result
	ack.Error = nil
	if extranonce1 == "" {
		miner.TxLock.Unlock()
		Handle_exception(miner, id, MINER_ERR_NOT_SUBSCRIBED)
		return
	}
	body, err := json.Marshal(ack)
	if err != nil {
		miner.Server.Logg.Error("[server]", zap.String("Marshal", err.Error()))
		miner.TxLock.Unlock()
		return
	}
	var body_string = string(body) + "\n"
	miner.Server.Logg.Debug("[server]", zap.String("tx", body_string))
	err = Conn_tx(miner.Conn, []byte(body_string))
	if err != nil {
		//delete(miner.Server.Miners, miner.MinerId)
		//miner.Server.Miners.Delete(miner.MinerId)
	}
	miner.Status = coin.MINER_STATUS_SUBSCRIBED
	miner.TxLock.Unlock()

	if miner.ZlogInit {
		miner.Zlog.Info().Msg(body_string)
	}
}

// server-->miner
func Handle_exception(miner *coin.MinerObj, Id float64, errId int) {
	miner.TxLock.Lock()
	var errors [3]interface{}
	errors[0] = errId
	switch errId {
	case MINER_ERR_UNKNOWN:
		errors[1] = "Other/Unknown"
		break
	case MINER_ERR_NOT_FOUND_JOB:
		errors[1] = "Job not found"
		break
	case MINER_ERR_STALED_JOB:
		errors[1] = "Job staled"
		break
	case MINER_ERR_DUP_SHARE:
		errors[1] = "Duplicate share"
		break
	case MINER_ERR_LOW_DIF_SHARE:
		errors[1] = "Low difficulty share"
		break
	case MINER_ERR_UNAUTH_WORKER:
		errors[1] = "Unauthorized worker"
		break
	case MINER_ERR_NOT_SUBSCRIBED:
		errors[1] = "Not subscribed"
		break
	case MINER_ERR_ILLEGAL_METHOD:
		errors[1] = "Illegal method"
		break
	case MINER_ERR_ILLEGAL_PARARMS:
		errors[1] = "Illegal params"
		break
	case MINER_ERR_IP_BANNED:
		errors[1] = "Ip banned"
		break
	case MINER_ERR_INVALID_USERNAME:
		errors[1] = "Invalid username"
		break
	case MINER_ERR_INTERNAL_ERROR:
		errors[1] = "Internal error"
		break
	case MINER_ERR_TIME_TOO_OLD:
		errors[1] = "Time too old"
		break
	case MINER_ERR_TIME_TOO_NEW:
		errors[1] = "Time too new"
		break
	case MINER_ERR_ILLEGAL_VERMASK:
		errors[1] = "Invalid version mask"
		break
	}
	errors[2] = nil
	var ack Exception_reply
	ack.ID = Id
	ack.Result = nil
	ack.Error = errors
	body, err := json.Marshal(ack)
	if err != nil {
		miner.Server.Logg.Debug("[server]", zap.String("fail to handle_exception", err.Error()))
		miner.TxLock.Unlock()
		return
	}
	var body_string = string(body) + "\n"

	err = Conn_tx(miner.Conn, []byte(body_string))
	if err != nil {
		miner.Server.Logg.Debug("[server]", zap.String("fail to handle_exception", err.Error()))
		//miner.Server.Miners.Delete(miner.MinerId)
		miner.TxLock.Unlock()
		return
	}
	miner.TxLock.Unlock()
	if miner.ZlogInit {
		miner.Zlog.Info().Msg(body_string)
	}
	//miner.Server.Logg.Debug("[server]", zap.String("tx", body_string))
}
func Handle_exception_str(miner *coin.MinerObj, Id string, errId int) {
	miner.TxLock.Lock()
	var errors [3]interface{}
	errors[0] = errId
	switch errId {
	case MINER_ERR_UNKNOWN:
		errors[1] = "Other/Unknown"
		break
	case MINER_ERR_NOT_FOUND_JOB:
		errors[1] = "Job not found"
		break
	case MINER_ERR_STALED_JOB:
		errors[1] = "Job staled"
		break
	case MINER_ERR_DUP_SHARE:
		errors[1] = "Duplicate share"
		break
	case MINER_ERR_LOW_DIF_SHARE:
		errors[1] = "Low difficulty share"
		break
	case MINER_ERR_UNAUTH_WORKER:
		errors[1] = "Unauthorized worker"
		break
	case MINER_ERR_NOT_SUBSCRIBED:
		errors[1] = "Not subscribed"
		break
	case MINER_ERR_ILLEGAL_METHOD:
		errors[1] = "Illegal method"
		break
	case MINER_ERR_ILLEGAL_PARARMS:
		errors[1] = "Illegal params"
		break
	case MINER_ERR_IP_BANNED:
		errors[1] = "Ip banned"
		break
	case MINER_ERR_INVALID_USERNAME:
		errors[1] = "Invalid username"
		break
	case MINER_ERR_INTERNAL_ERROR:
		errors[1] = "Internal error"
		break
	case MINER_ERR_TIME_TOO_OLD:
		errors[1] = "Time too old"
		break
	case MINER_ERR_TIME_TOO_NEW:
		errors[1] = "Time too new"
		break
	case MINER_ERR_ILLEGAL_VERMASK:
		errors[1] = "Invalid version mask"
		break
	}
	errors[2] = nil
	var ack Exception_reply_str
	ack.ID = Id
	ack.Result = nil
	ack.Error = errors
	body, err := json.Marshal(ack)
	if err != nil {
		miner.Server.Logg.Debug("[server]", zap.String("fail to handle_exception", err.Error()))
		miner.TxLock.Unlock()
		return
	}
	var body_string = string(body) + "\n"

	err = Conn_tx(miner.Conn, []byte(body_string))
	if err != nil {
		miner.Server.Logg.Debug("[server]", zap.String("fail to handle_exception", err.Error()))
		//miner.Server.Miners.Delete(miner.MinerId)
		miner.TxLock.Unlock()
		return
	}
	miner.TxLock.Unlock()
	if miner.ZlogInit {
		miner.Zlog.Info().Msg(body_string)
	}
	//miner.Server.Logg.Debug("[server]", zap.String("tx", body_string))
}

func InitMinerMhs(miner *coin.MinerObj, user string, minername string, minerindex string, miner_id string, status string, DbCtx *db.DbContext) {
	var k string

	k = user + "." + minername + "_" + minerindex

	m, ok := miner.Server.MMhs.Load(k)
	if ok {
		var mhs coin.MhsObj = m.(coin.MhsObj)
		mhs.StartSubmitTime = time.Now()
		mhs.Status = status
		mhs.MinerId = miner_id
		miner.Server.MMhs.Store(k, m)
		//miner.Server.Logg.Info("[server]", zap.String("exist mhs", k))
	} else {
		var mhs coin.MhsObj
		mhs.MinerId = miner_id

		mhs.StartSubmitTime = time.Now()
		mhs.Status = status
		mhs.Name = miner.Name

		mhs.User = user

		mhs.Miner = minername
		mhs.Index = minerindex

		mhs.StartDayTime = time.Now()

		mhs.Algo = -1

		miner.Server.MMhs.Store(k, mhs)
		//miner.Server.Logg.Info("[server]", zap.String("new mhs", k))
	}
}

func validateUsername(username string, min int, max int) bool {
	//
	if len(username) < min || len(username) > max {
		return false
	}

	//
	/*allowedChars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
	for _, char := range username {
		if !strings.ContainsRune(allowedChars, char) {
			return false
		}
	}*/

	//
	//pattern := `^[a-zA-Z0-9_]{4,15}$`
	pattern := fmt.Sprintf("^[a-zA-Z0-9_]{%d,%d}$", min, max)
	match, err := regexp.MatchString(pattern, username)
	if err != nil {
		//fmt.Println("Error matching pattern:", err)
		return false
	}
	return match
}

func Handle_extranonce(miner *coin.MinerObj, id float64) {
	miner.TxLock.Lock()

	var ack ExtranonceSubscribeGpu_reply
	ack.ID = id
	ack.Result = true
	ack.Jsonrpc = "2.0"

	body, err := json.Marshal(ack)
	if err != nil {
		miner.Server.Logg.Error("[server]", zap.String("Marshal", err.Error()))
		miner.TxLock.Unlock()
		return
	}
	var body_string = string(body) + "\n"
	//miner.Server.Logg.Debug("[server]", zap.String("tx", body_string))
	err = Conn_tx(miner.Conn, []byte(body_string))
	if err != nil {
		//delete(miner.Server.Miners, miner.MinerId)
		//miner.Server.Miners.Delete(miner.MinerId)
	}

	miner.TxLock.Unlock()

	if miner.ZlogInit {
		miner.Zlog.Info().Msg(body_string)
	}
}

func extractAndConvertDiff(password string) (float64, bool) {
	index := strings.Index(password, "d=")
	if index == -1 {
		return 0, false
	}

	valueStr := password[index+2:]

	value, err := strconv.ParseFloat(valueStr, 64)
	if err != nil {
		return 0, false
	}

	return value, true
}

func alphExtractAndConvertDiff(str string) (float64, bool) {
	if str == "0" {
		return 0, false
	}
	byteTarget := []byte(str)
	value := utility.Target2Diff(byteTarget)
	return value, true
}

func Handle_submitHashrate(miner *coin.MinerObj, msg string) {
	miner.TxLock.Lock()
	// randomNumber := rand.Intn(65536)
	// hexString := fmt.Sprintf("%04x", randomNumber)
	// method := "mining.set extranonce"
	// params := []string{hexString}
	// setExtranonce_msg := AlphSetExtranonce{Method: method, ID: nil, Params: params}
	// ex_msg, err := json.Marshal(setExtranonce_msg)
	// fmt.Println("extranonce:", string(ex_msg))
	// if err != nil {
	// 	fmt.Println("[server]", zap.String("Marshal", err.Error()))
	// }
	// Conn_tx(miner.Conn, ex_msg)
	prediff, ok := alphExtractAndConvertDiff(msg)
	// fmt.Println("难度初始化成功,初始化难度为:", prediff)
	if ok {
		if (prediff >= miner.Server.Config.Diff.DiffMin) && (prediff <= miner.Server.Config.Diff.DiffMax) {
			miner.Difficulty = prediff
		}
	}
	miner.TxLock.Unlock()
}

func AlphSetExtranonce() []byte {
	randomNumber := rand.Intn(65536)
	hexString := fmt.Sprintf("%04x", randomNumber)
	v_json := AlphExtranonce{
		ID:     nil,
		Method: "mining.set_extranonce",
		Params: []string{hexString},
	}
	v_json_bytes, err := json.Marshal(v_json)
	if err != nil {
		fmt.Println(err)
		return nil
	}
	fmt.Println("发送extranonce:", string(v_json_bytes))
	return []byte(string(v_json_bytes) + "\n")
}

// miner-->server
func Handle_authorize(miner *coin.MinerObj, id float64, auth_msg string, DbCtx *db.DbContext) bool {
	miner.TxLock.Lock()
	var s Authorize_msg
	var e error

	if e = json.Unmarshal([]byte(auth_msg), &s); e != nil {
		miner.Server.Logg.Error("[server]", zap.String("Unmarshal", e.Error()))
	}

	if s.Params[0] == "" {
		miner.Server.Logg.Error("[server]", zap.String("Handle_authorize err", s.Params[0]))
		miner.TxLock.Unlock()
		return false
	}

	var strArr []string

	if strings.Index(s.Params[0], ".") == -1 {
		miner.Server.Logg.Error("[server]", zap.String("user format err", s.Params[0]))
		miner.TxLock.Unlock()
		return false
	}

	strArr = strings.Split(s.Params[0], ".") //jjyykk.4x251.dash(jjyykk:user 4x251:miner)

	if strArr[0] == "" || strArr[1] == "" {
		miner.Server.Logg.Error("[server]", zap.String("user", strArr[0]), zap.String("miner", strArr[1]))
		miner.TxLock.Unlock()
		return false
	}

	if (!validateUsername(strArr[0], 3, 15)) || (!validateUsername(strArr[1], 1, 15)) {
		miner.TxLock.Unlock()
		miner.Server.Logg.Error("[server]", zap.String("invalid user", strArr[0]))
		Handle_exception(miner, id, MINER_ERR_INVALID_USERNAME)
		return false
	}

	miner.Server.Logg.Warn("[server]", zap.String("user", strArr[0]), zap.String("miner", strArr[1]))

	if miner.Server.Config.Host.Auth {
		if !db.CheckUserIsPermitted(strArr[0]) {
			miner.TxLock.Unlock()
			miner.Server.Logg.Error("[server]", zap.String("not found user", strArr[0]))
			Handle_exception(miner, id, MINER_ERR_INVALID_USERNAME)
			return false
		}
	}

	if miner.Server.CoinCtx.Coin == "alph" {
		// count := 0
		// for i := 0; i < 10; i++ {
		// 	if miner.Difficulty != 0 {
		// 		// fmt.Println(miner.User, " ", miner.Miner, "难度初始化成功!")
		// 		break
		// 	}
		// 	time.Sleep(time.Second * 1)
		// 	count++
		// }

	} else {
		prediff, ok := extractAndConvertDiff(s.Params[1])

		if ok {
			if (prediff >= miner.Server.Config.Diff.DiffMin) && (prediff <= miner.Server.Config.Diff.DiffMax) {
				miner.Difficulty = prediff
			}
		}
	}

	miner.User = strArr[0]
	miner.Miner = strArr[1]
	miner.Authorized = true

	var ack Authorize_reply
	ack.ID = id
	ack.Result = true
	ack.Error = nil
	body, err := json.Marshal(ack)

	if err != nil {
		miner.Server.Logg.Error("[server]", zap.String("Marshal", err.Error()))
		miner.TxLock.Unlock()
		return false
	}

	var body_string = string(body) + "\n"
	// fmt.Println("身份验证矿池回复:", body_string)
	err = Conn_tx(miner.Conn, []byte(body_string))

	if err != nil {
		//delete(miner.Server.Miners, miner.MinerId)
		//miner.Server.Miners.Delete(miner.MinerId)
	}

	//miner.Server.Logg.Debug("[server]", zap.String("tx", body_string))
	miner.Status = coin.MINER_STATUS_AUTHORIZED
	miner.TxLock.Unlock()

	mlogfile := "./logs/" + miner.Name + "/" + miner.User + "_" + miner.Miner + "_" + fmt.Sprint(miner.MinerIndex) + ".log"
	logFile := &lumberjack.Logger{
		Filename:   mlogfile,
		MaxSize:    1,
		MaxBackups: 3,
		MaxAge:     31,
		Compress:   true,
	}
	miner.LogR = logFile
	zerolog.TimeFieldFormat = time.RFC3339
	miner.Zlog = zerolog.New(logFile).With().Timestamp().Logger()
	miner.ZlogInit = true
	miner.Zlog.Info().Msg(auth_msg)
	miner.Zlog.Info().Msg(body_string)
	return true

}

// server-->miner
func Set_difficulty(miner *coin.MinerObj) {
	miner.TxLock.Lock()
	var msg Difficulty_msg
	msg.ID = nil
	msg.Method = "mining.set_difficulty"
	msg.Params[0] = miner.Difficulty

	body, err := json.Marshal(msg)
	if err != nil {
		miner.Server.Logg.Error("[server]", zap.String("Marshal", err.Error()))
		miner.TxLock.Unlock()
		return
	}

	var body_string = string(body) + "\n"
	err = Conn_tx(miner.Conn, []byte(body_string))
	if err != nil {
		//delete(miner.Server.Miners, miner.MinerId)
		//miner.Server.Miners.Delete(miner.MinerId)
	}
	//miner.Server.Logg.Debug("[server]", zap.String("tx", body_string))
	miner.TxLock.Unlock()

	if miner.ZlogInit {
		miner.Zlog.Info().Msg(body_string)
	}
}

/*func Set_difficulty_nexa(miner *coin.MinerObj) {
	target_new, err_to := utility.DiffToTarget(miner.Difficulty)
	if err_to != nil {
		miner.Server.Logg.Error("[server]", zap.String("DiffToTarget", err_to.Error()))
		return
	}
	miner.Target = target_new
	miner.TxLock.Lock()
	var msg DifficultyNexa_msg
	msg.ID = nil
	msg.Method = "mining.set_target"
	target := fmt.Sprintf("%064x\n", miner.Target.Bytes())
	msg.Params[0] = target
	body, err := json.Marshal(msg)
	if err != nil {
		miner.Server.Logg.Error("[server]", zap.String("Marshal", err.Error()))
		miner.TxLock.Unlock()
		return
	}

	var body_string = string(body) + "\n"
	err = Conn_tx(miner.Conn, []byte(body_string))
	if err != nil {
		//delete(miner.Server.Miners, miner.MinerId)
		//miner.Server.Miners.Delete(miner.MinerId)
	}
	miner.Server.Logg.Debug("[server]", zap.String("tx", body_string))
	miner.TxLock.Unlock()
	if miner.ZlogInit {
	miner.Zlog.Info().Msg(body_string)
	}
}*/

func Set_difficulty_nexa(miner *coin.MinerObj) {
	miner.TxLock.Lock()
	var msg DifficultyNexaGpu_msg
	msg.ID = nil
	msg.Method = "mining.set_difficulty"
	msg.Params[0] = miner.Difficulty
	msg.Jsonrpc = "2.0"
	body, err := json.Marshal(msg)
	if err != nil {
		miner.Server.Logg.Error("[server]", zap.String("Marshal", err.Error()))
		miner.TxLock.Unlock()
		return
	}

	var body_string = string(body) + "\n"
	err = Conn_tx(miner.Conn, []byte(body_string))
	if err != nil {
		//delete(miner.Server.Miners, miner.MinerId)
		//miner.Server.Miners.Delete(miner.MinerId)
	}
	//miner.Server.Logg.Debug("[server]", zap.String("tx", body_string))
	miner.TxLock.Unlock()

	if miner.ZlogInit {
		miner.Zlog.Info().Msg(body_string)
	}
}

func removeExpiredJobs(miner *coin.MinerObj, checkExpiration bool) {
	var removes []string
	miner.LockForJobs.Lock()
	defer miner.LockForJobs.Unlock()

	for element := miner.JobList.Front(); element != nil; {
		entry, isValidEntry := element.Value.(coin.JobListEntry)
		next := element.Next()

		if isValidEntry {
			if checkExpiration {
				if time.Since(entry.Ts) >= time.Duration(coin.LOCAL_JOBS_EXPIRED_TIME)*time.Second {
					removes = append(removes, entry.Job_id)
					miner.JobList.Remove(element)
				}
			} else {
				removes = append(removes, entry.Job_id)
				miner.JobList.Remove(element)
			}
		}
		element = next
	}

	for _, jobID := range removes {
		miner.Jobs.Delete(jobID)
	}
}

func UpdateJobs(miner *coin.MinerObj) {
	/*var removes []string
	miner.LockForJobs.Lock()
	defer miner.LockForJobs.Unlock()
	//for e := miner.JobList.Front(); e != nil; e = e.Next() {
	for e := miner.JobList.Front(); e != nil; {
		entry, ok := e.Value.(coin.JobListEntry)
		if ok {
			//if time.Now().Sub(entry.Ts) >= time.Duration(coin.LOCAL_JOBS_EXPIRED_TIME)*time.Second {
			if time.Since(entry.Ts) >= time.Duration(coin.LOCAL_JOBS_EXPIRED_TIME)*time.Second {
				removes = append(removes, entry.Job_id)
				next := e.Next()
				miner.JobList.Remove(e)
				e = next
				continue
			}
		}
		e = e.Next()
	}
	//miner.LockForJobs.Unlock()
	for i := range removes {
		miner.Jobs.Delete(removes[i])
	}*/
	removeExpiredJobs(miner, true)
}

func StaleAllJobs(miner *coin.MinerObj) {
	/*var removes []string
	miner.LockForJobs.Lock()
	defer miner.LockForJobs.Unlock()
	//for e := miner.JobList.Front(); e != nil; e = e.Next() {
	for e := miner.JobList.Front(); e != nil; {
		entry, ok := e.Value.(coin.JobListEntry)
		if ok {
			removes = append(removes, entry.Job_id)
			next := e.Next()
			miner.JobList.Remove(e)
			e = next
			continue
		}
		e = e.Next()
	}
	//miner.LockForJobs.Unlock()
	for i := range removes {
		miner.Jobs.Delete(removes[i])
	}*/
	removeExpiredJobs(miner, false)
}

func AddAndUpdateJob(miner *coin.MinerObj) {
	entry := coin.JobListEntry{
		Job_id: miner.Job.Job_id,
		Ts:     time.Now(),
	}

	miner.LockForJobs.Lock()
	defer miner.LockForJobs.Unlock()

	miner.JobList.PushFront(entry)

	if miner.JobList.Len() > int(coin.LOCAL_JOBS_TOTAL_SIZE) {
		if e := miner.JobList.Back(); e != nil {
			if oldestEntry, ok := e.Value.(coin.JobListEntry); ok {
				miner.JobList.Remove(e)
				miner.Jobs.Delete(oldestEntry.Job_id)
			}
		}
	}
}

func Notify(miner *coin.MinerObj) {
	miner.TxLock.Lock()
	if !((miner.Status == coin.MINER_STATUS_AUTHORIZED) || (miner.Status == coin.MINER_STATUS_RUNNING)) {
		miner.TxLock.Unlock()
		return
	}
	miner.TxLock.Unlock()
	if miner.DifficultyNext > -1 {
		ratio := miner.DifficultyNext / miner.Difficulty
		if ratio > 1.1 || ratio < 0.9 {
			miner.Difficulty = miner.DifficultyNext
			miner.DifficultyNext = -1
			//Set_difficulty(miner)
			miner.Server.CoinCtx.SetDifficulty(miner)
		} else {
			miner.DifficultyNext = -1
		}
	}
	miner.TxLock.Lock()
	//log.Println("[server]extra1, id", miner.Job.Extranonce1, miner.Job.Job_id, miner.MinerId)
	var params [9]interface{}
	var tlist []string = make([]string, 0)
	idb := make([]byte, 4)
	binary.BigEndian.PutUint32(idb, miner.JobId)
	miner.Job.Job_id = hex.EncodeToString(idb)
	params[0] = miner.Job.Job_id
	if len(miner.Job.PrevblockS) > 0 {
		params[1] = miner.Job.PrevblockBig
	} else {
		p_big := utility.Convert_big_endian(miner.Job.Prevblock.CloneBytes())
		params[1] = hex.EncodeToString(p_big)
	}
	params[2] = miner.Job.Coinbase1
	params[3] = miner.Job.Coinbase2
	params[4] = tlist

	miner.CurHeight = miner.Job.Height

	if miner.Job.Transactions != nil {
		if len(*miner.Job.Transactions) > 0 {
			params[4] = miner.Job.Transactions

			/*miner.Server.Logg.Error("[notify]", zap.String("coinbase1", miner.Job.Coinbase1), zap.String("coinbase2", miner.Job.Coinbase2), zap.Uint32("height", miner.Job.Height))
			for i := 0; i < len(*miner.Job.Transactions); i++ {
				miner.Server.Logg.Error("[notify]", zap.String("trans", (*miner.Job.Transactions)[i]))
			}*/

		}
	}
	vb := make([]byte, 4)
	binary.LittleEndian.PutUint32(vb, uint32(miner.Job.Version))
	params[5] = hex.EncodeToString(vb)
	bb := make([]byte, 4)
	binary.LittleEndian.PutUint32(bb, miner.Job.Bits)
	params[6] = hex.EncodeToString(bb)
	t := miner.Job.Timestamp.Unix()
	if t > int64(^uint32(0)) {
		tb := make([]byte, 8)
		binary.LittleEndian.PutUint64(tb, uint64(t))
		params[7] = hex.EncodeToString(tb)
	} else {
		tb := make([]byte, 4)
		binary.LittleEndian.PutUint32(tb, uint32(t))
		params[7] = hex.EncodeToString(tb)
	}
	if miner.Reconnect {
		params[8] = true
		miner.Reconnect = false
	} else {
		params[8] = miner.Job.IsClean
	}
	miner.Job.JobDifficulty = miner.Difficulty

	//miner.Jobs[miner.Job.Job_id] = miner.Job
	miner.Jobs.LoadOrStore(miner.Job.Job_id, miner.Job)

	/*var entry coin.JobListEntry
	entry.Job_id = miner.Job.Job_id
	entry.Ts = time.Now()

	miner.LockForJobs.Lock()
	miner.JobList.PushFront(entry)
	var removes string = ""
	if miner.JobList.Len() > int(coin.LOCAL_JOBS_TOTAL_SIZE) {
		e := miner.JobList.Back()
		entry := e.Value.(coin.JobListEntry)
		removes = entry.Job_id
		miner.JobList.Remove(e)
	}
	miner.LockForJobs.Unlock()
	if len(removes) > 0 {
		miner.Jobs.Delete(removes)
	}*/
	AddAndUpdateJob(miner)
	UpdateJobs(miner)

	//miner.LastJobId = miner.Job.Job_id
	miner.JobId++

	var msg Notify_msg
	msg.ID = nil
	msg.Method = "mining.notify"
	msg.Params = params
	body, err := json.Marshal(msg)
	if err != nil {
		miner.Server.Logg.Error("[server]", zap.String("Marshal", err.Error()))
		miner.TxLock.Unlock()
		return
	}
	var body_string = string(body) + "\n"
	err = Conn_tx(miner.Conn, []byte(body_string))
	if err != nil {
		//delete(miner.Server.Miners, miner.MinerId)
		//miner.Server.Miners.Delete(miner.MinerId)
	}
	//miner.Server.Logg.Debug("[server]", zap.String("tx", body_string))
	miner.TxLock.Unlock()

	if miner.ZlogInit {
		miner.Zlog.Info().Msg(body_string)
	}
}

type Submit_ack struct {
	Result bool        `json:"result"`
	ID     float64     `json:"id"`
	Error  interface{} `json:"error"`
}

type Submit_ack_str struct {
	Result bool        `json:"result"`
	ID     string      `json:"id"`
	Error  interface{} `json:"error"`
}

// server-->miner
func parse_miner_notify(miner *coin.MinerObj, msg msg.StratumJob) int {
	miner.Job.Version = msg.Version
	miner.Job.Prevblock = msg.Prevblock
	miner.Job.Coinbase1 = msg.Coinbase1
	miner.Job.Coinbase2 = msg.Coinbase2
	miner.Job.Bits = msg.Bits
	miner.Job.Timestamp = msg.Timestamp
	miner.Job.Target = msg.Target
	miner.Job.PrevblockS = msg.PrevblockS
	miner.Job.PrevblockBig = msg.PrevblockBig
	miner.Job.Transactions = msg.Transactions
	miner.Job.BitsS = msg.BitsS
	miner.Job.Height = msg.Height
	miner.Job.Extranonce2_size = msg.Extranonce2_size
	miner.Job.TransData = msg.TransData
	miner.Job.Payloadstart = msg.Payloadstart
	miner.Job.Segwit = msg.Segwit
	miner.Job.IsClean = msg.IsClean
	miner.Job.Mintime = msg.Mintime
	miner.ServerTargetS = msg.Target
	vb := make([]byte, 4)
	binary.LittleEndian.PutUint32(vb, uint32(msg.Version))
	vBuffer := bytes.NewBuffer(vb)
	binary.Read(vBuffer, binary.BigEndian, &(miner.Version))
	//log.Printf("version %04x, %04x", miner.Version, msg.Version)
	//miner.Server.Logg.Info("[server]", zap.Int32("miner.Version", miner.Version), zap.Int32("msg.Version", msg.Version))
	return 1
}

func Send_reconnect_msg(miner *coin.MinerObj) bool {
	var msg Reconnect_msg
	msg.ID = nil
	msg.Method = "client.reconnect"
	msg.Params = nil
	body, err := json.Marshal(msg)
	if err != nil {
		miner.Server.Logg.Error("[server]", zap.String("failed to Send_reconnect_msg", err.Error()), zap.String("user", miner.User), zap.String("miner", miner.Miner))
		return false
	}
	body_string := string(body) + "\n"
	err = Conn_tx(miner.Conn, []byte(body_string))
	if err != nil {
		miner.Server.Logg.Error("[server]", zap.String("failed to Send_reconnect_msg", err.Error()), zap.String("user", miner.User), zap.String("miner", miner.Miner))
		return false
	}
	return true
}

func UpdateMhs(miner *coin.MinerObj, accept bool, diff float64, algo int, DbCtx *db.DbContext) {
	var k string

	k = miner.User + "." + miner.Miner + "_" + fmt.Sprint(miner.MinerIndex)

	v, ok := miner.Server.MMhs.Load(k)
	if ok {
		var m coin.MhsObj = v.(coin.MhsObj)
		var item coin.MhsItem
		item.Tt = time.Now()
		item.Diff = diff
		if accept {
			m.Accepts = append(m.Accepts, item)
		} else {
			m.Rejects = append(m.Rejects, item)
		}
		m.Status = miner.Status
		m.MinerId = miner.MinerId
		if m.Algo < 0 {
			m.Algo = algo
		}
		miner.Server.MMhs.Store(k, m)
		var mhsItem cache.CacheMhsItem
		mhsItem.Tt = item.Tt.Format(time.RFC3339)
		mhsItem.Diff = item.Diff
		if accept {
			cache.StoreMhsCache(miner.Server.RedisClient, miner.Server.MinerType, miner.User, miner.Miner, fmt.Sprint(miner.MinerIndex), "accepts", mhsItem)
		} else {
			cache.StoreMhsCache(miner.Server.RedisClient, miner.Server.MinerType, miner.User, miner.Miner, fmt.Sprint(miner.MinerIndex), "rejects", mhsItem)
		}
		//miner.Server.Logg.Info("[mhs]", zap.String("UpdateMhs", k), zap.Int("accepts", len(m.Accepts)), zap.Int("rejects", len(m.Rejects)), zap.Int("algo", m.Algo))
	}
}

func UpdateMhsStatus(miner *coin.MinerObj, DbCtx *db.DbContext) {
	var k string

	k = miner.User + "." + miner.Miner + "_" + fmt.Sprint(miner.MinerIndex)

	v, ok := miner.Server.MMhs.Load(k)
	if ok {
		var m coin.MhsObj = v.(coin.MhsObj)
		m.Status = miner.Status
		m.MinerId = miner.MinerId
		miner.Server.MMhs.Store(k, m)
		//miner.Server.Logg.Info("[mhs]", zap.String("UpdateMhsStatus", k), zap.String("update status", m.Status))
	}
}