// dgb.go
package dgb

import (
	//"database/sql"
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"

	//"log"
	//"math"
	"math/big"
	"strings"

	"fmt"
	"pool/internal/msg"
	"pool/internal/server/coin"
	"pool/internal/server/dbif"
	"pool/internal/stratum"
	"pool/internal/utility"
	"time"

	"github.com/btcsuite/btcd/wire"

	_ "github.com/mattn/go-sqlite3"

	"go.uber.org/zap"
)

const SERVER_DGB_VERSION string = "dgb v3.0j"

const DGB_BLOCK_VERSION_SKEIN int32 = 3
const DGB_BLOCK_VERSION_QUBIT int32 = 4
const DGB_BLOCK_VERSION_ODO int32 = 7

type ServerDgbContext struct {
	ServerCtx *coin.ServerContext
	logg      *zap.Logger

	Job msg.StratumJob
}

var logg *zap.Logger
var ServerDgbCtx ServerDgbContext

const truediffone float64 = 26959535291011309493156476344723991336010898738574164086137773096960.0
const bits192 float64 = 6277101735386680763835789423207666416102355444464034512896.0
const bits128 float64 = 340282366920938463463374607431768211456.0
const bits64 float64 = 18446744073709551616.0

func target2float(target []byte) float64 {
	var b64 float64 = float64(binary.LittleEndian.Uint64(target[24:32])) * bits192
	b64 += (float64(binary.LittleEndian.Uint64(target[16:24])) * bits128)
	b64 += (float64(binary.LittleEndian.Uint64(target[8:16])) * bits64)
	b64 += (float64(binary.LittleEndian.Uint64(target[0:8])))
	return b64
}

// convert target to difficulty
func target2diff(target []byte, version int32) float64 {
	var f64 float64 = truediffone
	var fcut64 float64 = target2float(target)
	//log.Println("diff", f64, fcut64, f64/fcut64*65536)
	//return f64 / fcut64
	var diff float64 = f64 / fcut64
	algo := (version & DGB_BLOCK_VERSION_ALGO) >> 9
	switch algo {
	case DGB_BLOCK_VERSION_SKEIN:
		return diff * 1
	case DGB_BLOCK_VERSION_QUBIT:
		return diff * 1
	case DGB_BLOCK_VERSION_ODO:
		return diff * 1
	}
	return diff
}

func handle_submit(miner *coin.MinerObj, id float64, miner_user string, job_id string, nonce2 string, ntime string, nonce string) (bool, bool, bool) {
	var submit_item coin.BlockMsg
	/*var user_blk_item coin.UserBlockMsg*/
	var pool_blk_item coin.PoolBlkMsg

	var blk_detail_height int64
	var blk_detail_hash string
	var blk_detail_success bool
	var blk_detail_miner_diff float64
	var blk_detail_pool_diff float64

	if miner.Authorized != true {
		miner.ErrOthers = miner.ErrOthers + 1
		stratum.Handle_exception(miner, id, stratum.MINER_ERR_UNAUTH_WORKER)
		stratum.Send_reconnect_msg(miner)
		return false, false, false
	}
	var new_found bool = false
	var ack stratum.Submit_ack
	ack.ID = id
	ack.Result = true
	//logg.Warn("[server]", zap.String("user", miner.User), zap.String("miner", miner.Miner))
	//logg.Debug("[server]", zap.Float64("id", id), zap.String("job_id", job_id))
	//logg.Debug("[server]", zap.String("nonce2", nonce2), zap.String("ntime", ntime), zap.String("nonce", nonce))
	//stratum.UpdateJobs(miner)
	v, ok := miner.Jobs.Load(job_id)
	if ok {
		job := v.(msg.StratumJob)

		if job.Height < miner.CurHeight-1 {
			ack.Result = false
			stratum.Handle_exception(miner, id, stratum.MINER_ERR_STALED_JOB)
			miner.ErrStaleds = miner.ErrStaleds + 1
			return false, false, false
		}

		//logg.Debug("[server]", zap.Uint64("ntime", nt), zap.Uint64("mintime", uint64(job.Mintime)), zap.Uint64("jobtime", jt_reverse))
		/*if nt < uint64(job.Mintime) {
			ack.Result = false
			util.Handle_exception(miner, id, util.MINER_ERR_TIME_TOO_OLD)
		} else if nt > jt_reverse+uint64(600) {
			ack.Result = false
			util.Handle_exception(miner, id, util.MINER_ERR_TIME_TOO_NEW)
		} else */{
			if miner.LastNonce != nonce {

				miner.LastNonce = nonce
				//job.Nonce = nonce
				//job.Extranonce2 = nonce2
				//logg.Debug("[server]", zap.Uint32("height", job.Height), zap.String("target", job.Target))
				if miner.ZlogInit {
					miner.Zlog.Info().Msg("height " + fmt.Sprintf("%d", job.Height) + " target " + job.Target + " extra1 " + job.Extranonce1 + " size " + fmt.Sprintf("%d", job.Extranonce2_size) + " " + miner.User + "." + miner.Miner)
				}

				//job := v.(msg.StratumJob)
				vb := make([]byte, 4)
				binary.LittleEndian.PutUint32(vb, uint32(job.Version))
				vBuffer := bytes.NewBuffer(vb)
				binary.Read(vBuffer, binary.BigEndian, &(miner.Version))

				job.Nonce = nonce
				job.Extranonce2 = nonce2
				//logg.Debug("[server]", zap.Uint32("height", job.Height), zap.String("target", job.Target))
				var calc_hash []byte
				var header wire.BlockHeader

				calc_hash, header = coin.BuildBlockHash(&(job), true, Build_DgbPowHash)

				//logg.Debug("[server]", zap.String("hash in", submit_item.Header))
				//calc_hash, header := util.BuildBlockHash(&(job), true, Build_PowHash)
				//logg.Debug("[server]", zap.String("calc_hash", hex.EncodeToString(calc_hash)) /*, zap.String("merkle root", hex.EncodeToString(merkle_root))*/)
				if miner.ZlogInit {
					printbuf := bytes.NewBuffer(make([]byte, 0, wire.MaxBlockHeaderPayload))
					header.Serialize(printbuf)
					miner.Zlog.Info().Msg("hash in " + hex.EncodeToString(printbuf.Bytes()) + " calc_hash " + hex.EncodeToString(calc_hash) + " " + miner.User + "." + miner.Miner)
				}
				submit_target := new(big.Int)
				hashs, _ := utility.ReverseS(hex.EncodeToString(calc_hash))
				hashb, _ := hex.DecodeString(hashs)
				submit_target.SetBytes(hashb)
				calc_diff := target2diff(calc_hash, miner.Version)
				//log.Printf("diff,calc_diff:%f difficulty:%f ", calc_diff, miner.Difficulty)
				//logg.Warn("[server]", zap.String("user", miner.User+"."+miner.Miner), zap.Float64("target diff", miner.Difficulty), zap.Float64("submit diff", calc_diff))
				if miner.ZlogInit {
					miner.Zlog.Info().Msg(miner.User + "." + miner.Miner + " target diff " + fmt.Sprintf("%f", (job.JobDifficulty)) + " submit diff " + fmt.Sprintf("%f", (calc_diff)))
				}
				//logg.Debug("[server]", zap.String("target", miner.Target.String()), zap.Any("bytes", miner.Target.Bytes()))
				//logg.Info("[server]", zap.Float64("target diff", miner.Difficulty), zap.Float64("submit diff", calc_diff), zap.String("target", hex.EncodeToString(miner.Target.Bytes())))

				//if calc_diff < miner.Difficulty {
				if calc_diff < job.JobDifficulty {
					//gpu protocol handler
					/*for i := 0; i < 8; i++ {
						temp_nonce := header.Nonce[8+i]
						header.Nonce[8+i] = header.Nonce[i]
						header.Nonce[i] = temp_nonce
					}
					submit_item.Header = hex.EncodeToString(DgbBlockHeaderToBytes(header))
					calc_hash = BuildPowHash(header)
					logg.Debug("[server]", zap.String("hash in", hex.EncodeToString(DgbBlockHeaderToBytes(header))))*/
					//logg.Debug("[server]", zap.String("calc_hash", hex.EncodeToString(calc_hash)) /*, zap.String("merkle root", hex.EncodeToString(merkle_root))*/)
					//submit_target = new(big.Int)
					/*submit_target.SetBytes(calc_hash)
					calc_diff = utility.Target2Diff(calc_hash)
					logg.Warn("[server]", zap.String("user", miner.User+"."+miner.Miner), zap.Float64("target diff", miner.Difficulty), zap.Float64("submit diff", calc_diff))
					if calc_diff < miner.Difficulty {
					*/
					ack.Result = false
					miner.ErrLowDiffs = miner.ErrLowDiffs + 1
					stratum.Handle_exception(miner, id, stratum.MINER_ERR_LOW_DIF_SHARE)
					return false, false, false
					//}

				}
				//logg.Warn("[server]", zap.String("pow", hex.EncodeToString(submit_target.Bytes())), zap.String("target", hex.EncodeToString(miner.ServerTarget.Bytes())))
				//submit_target.Text(16)
				/*if submit_target.Cmp(miner.ServerTarget) <= 0 {*/
				//log.Println("[server]server_target", miner.ServerTargetS)
				//stb, _ := hex.DecodeString(miner.ServerTargetS)
				stb, _ := hex.DecodeString(job.Target)
				//logg.Info("[server]", zap.String("target", job.Target))
				//server_diff := Target2Diff(common.Reverse(stb))
				server_diff := target2diff(utility.Reverse(stb), miner.Version)
				//log.Printf("[server]server_diff %f", server_diff)
				//logg.Info("[server]", zap.Float64("calc_diff", calc_diff), zap.Float64("miner.Difficulty", miner.Difficulty), zap.Float64("server_diff", server_diff))
				//logg.Debug("[server]", zap.String("ServerTargetS", miner.ServerTargetS))
				network_target := new(big.Int)
				network_target.SetBytes(stb)
				//logg.Info("[server]", zap.Float64("calc_diff", calc_diff), zap.Float64("miner.Difficulty", miner.Difficulty), zap.Float64("server_diff", server_diff))
				//logg.Debug("[server]", zap.String("submit_target", hex.EncodeToString(submit_target.Bytes())), zap.String("network_target", hex.EncodeToString(network_target.Bytes())), zap.String("target", hex.EncodeToString(miner.ServerTarget.Bytes())), zap.Int("cmp", network_target.Cmp(submit_target)))
				if miner.ZlogInit {
					miner.Zlog.Info().Msg(miner.User + "." + miner.Miner + " calc_diff " + fmt.Sprintf("%f", (calc_diff)) + " miner.Difficulty " + fmt.Sprintf("%f", (miner.Difficulty)) + " server_diff " + fmt.Sprintf("%f", (server_diff)))
					miner.Zlog.Info().Msg(miner.User + "." + miner.Miner + " submit_target " + hex.EncodeToString(submit_target.Bytes()) + " network_target " + hex.EncodeToString(network_target.Bytes()) + " target " + hex.EncodeToString(miner.ServerTarget.Bytes()) + " cmp " + fmt.Sprintf("%d", (network_target.Cmp(submit_target))))
				}

				submit_item.Hash = hex.EncodeToString(calc_hash)
				submit_item.Target = hex.EncodeToString(miner.Target.Bytes())
				submit_item.Submit_target = hex.EncodeToString(calc_hash)
				submit_item.Height = int64(job.Height)
				submit_item.Pow = hex.EncodeToString(calc_hash)
				submit_item.Net_target = hex.EncodeToString(network_target.Bytes())

				/*user_blk_item.Height = int64(job.Height)
				user_blk_item.Hash = hex.EncodeToString(calc_hash)
				user_blk_item.Pow = hex.EncodeToString(calc_hash)
				user_blk_item.Net_target = hex.EncodeToString(network_target.Bytes())*/

				pool_blk_item.Height = int64(job.Height)
				pool_blk_item.Hash = hex.EncodeToString(calc_hash)
				pool_blk_item.Pow = hex.EncodeToString(calc_hash)
				pool_blk_item.Net_target = hex.EncodeToString(network_target.Bytes())

				blk_detail_height = int64(job.Height)
				blk_detail_hash = hex.EncodeToString(calc_hash)
				blk_detail_success = false
				blk_detail_miner_diff = miner.Difficulty
				blk_detail_pool_diff = miner.Server.RefDifficulty

				if ack.Result == true {
					/*if miner.CurHeight != 0 && miner.CurHeight == job.Height {
						return
					}*/

					//if true {
					if (calc_diff >= server_diff) || (network_target.Cmp(submit_target) >= 0) {
						miner.Server.SubIdx++

						hdbuf := bytes.NewBuffer(make([]byte, 0, wire.MaxBlockHeaderPayload))
						err := header.Serialize(hdbuf)
						if err != nil {
							logg.Error("[server]", zap.String("header Serialize", err.Error()))
						} else {
							//pow := chainhash.DoubleHashB(hdbuf.Bytes())
							//blk_hash := hex.EncodeToString(pow)
							Produce_block_submit(miner, header, &job, submit_item.Hash, miner.Server.SubIdx)
							miner.SubmitIndex++
							miner.Submits = miner.Submits + 1
							//miner.CurHeight = job.Height
							new_found = true
						}
					}
				}
			} else {

				miner.LastNonce = nonce
				ack.Result = false
				stratum.Handle_exception(miner, id, stratum.MINER_ERR_DUP_SHARE)
				miner.ErrDuplicates = miner.ErrDuplicates + 1
				return false, false, false
			}
		}
	} else {
		ack.Result = false
		stratum.Handle_exception(miner, id, stratum.MINER_ERR_NOT_FOUND_JOB)
		miner.ErrStaleds = miner.ErrStaleds + 1
		return false, false, false
	}

	miner.LastJobId = job_id

	ack.Error = nil
	body, err := json.Marshal(ack)
	if err != nil {
		//logg.Error("[server]", zap.String("Marshal", err.Error()))
		if miner.ZlogInit {
			miner.Zlog.Info().Msg(miner.User + "." + miner.Miner + " handle_submit Marshal " + err.Error())
		}
		miner.ErrOthers = miner.ErrOthers + 1
		stratum.Handle_exception(miner, id, stratum.MINER_ERR_UNKNOWN)
		return false, false, false
	}

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

	if ack.Result {

		miner.Accepts += miner.Difficulty
		miner.M5Accepts += miner.Difficulty

		miner.VarDiffOpt.SubmitShares += miner.Difficulty

	} else {
		miner.Rejects += miner.Difficulty

	}
	now := time.Now()
	if miner.Server.Config.Diff.Filter == "kalman" {
		if ack.Result {
			share_interval := now.Sub(miner.LastSubmitime).Seconds()
			mhs := miner.Difficulty * share_interval
			diff_next, kalman_p := miner.DiffHandler.Handler(miner.Difficulty, share_interval)
			mhs_est := diff_next * miner.Server.Config.Diff.DiffAdjustInterval

			ratio := diff_next / miner.Difficulty
			if ratio > 0 {
				if now.Sub(miner.StartSubmitTime).Seconds() > 180 {
					if ratio >= 2 {
						//miner.DifficultyNext = math.Ceil(diff_next*100) / 100
						miner.DifficultyNext = diff_next * 10000000 / 10000000
					} else if ratio <= 0.5 {
						//miner.DifficultyNext = math.Ceil(diff_next*100) / 100
						miner.DifficultyNext = diff_next * 10000000 / 10000000
					} else {
					}
				} else {
					//miner.DifficultyNext = math.Ceil(diff_next*100) / 100
					miner.DifficultyNext = diff_next * 10000000 / 10000000
					/*if ratio >= 1.1 {
						miner.DifficultyNext = math.Ceil(diff_next*100) / 100
					} else if ratio <= 0.8 {
						miner.DifficultyNext = math.Ceil(diff_next*100) / 100
					} else {
					}*/
				}
			}
			if miner.DifficultyNext > 0.0 {
				if miner.DifficultyNext < miner.VarDiffOpt.MinDiff {
					miner.DifficultyNext = miner.VarDiffOpt.MinDiff
				} else if miner.DifficultyNext > miner.VarDiffOpt.MaxDiff {
					miner.DifficultyNext = miner.VarDiffOpt.MaxDiff
				}
			}
			//miner.VarDiffOpt.LastCalcTime = now

			if miner.Server.Config.Diff.Dbg {
				coin.New_diff_into_db(miner.User, miner.Miner, fmt.Sprint(miner.MinerIndex), miner.Difficulty, diff_next, kalman_p, share_interval, mhs, mhs_est)
			}

			//log.Println("diff adjust", ratio, diff_next, miner.Difficulty, miner.DifficultyNext)
		}
	} else {
		// submit time < DiffAdjustInterval,then up adjust diff
		if now.Sub(miner.LastSubmitime).Seconds() < miner.Server.Config.Diff.DiffAdjustInterval {
			if ack.Result {
				if miner.VarDiffOpt.Uptimes++; miner.VarDiffOpt.Uptimes >= coin.DIFFICULTY_WAIT_TIMES {
					coin.VarAdjustDifficulty(miner, coin.UP_DIFF)
					miner.VarDiffOpt.LastCalcTime = now
				}
			}
		} else {
			miner.VarDiffOpt.Uptimes = 0
		}

		// submit time > 2 * DiffAdjustInterval,then down adjust diff
		if now.Sub(miner.LastSubmitime).Seconds() > miner.Server.Config.Diff.DiffAdjustInterval*2 {
			if ack.Result {
				if miner.VarDiffOpt.Downtimes++; miner.VarDiffOpt.Downtimes >= coin.DIFFICULTY_WAIT_TIMES {
					coin.VarAdjustDifficulty(miner, coin.DOWN_DIFF)
					miner.VarDiffOpt.LastCalcTime = now
				}
			}
		} else {
			miner.VarDiffOpt.Downtimes = 0
		}
	}
	if ack.Result {
		miner.LastSubmitime = now
		miner.VarDiffOpt.LastSubmitTime = now
	}
	var duration float64 = float64(now.Sub(miner.StartSubmitTime)) / 1000000000

	if duration < 1 {
		duration = 1
	}
	diffOneShareHashesAvg := uint64(0x00000000FFFFFFFF)
	miner.AverageHashrate = miner.Accepts * float64(diffOneShareHashesAvg) / duration / 1000000
	var m5_duration float64 = float64(now.Sub(miner.M5SubmitTime)) / 1000000000
	if m5_duration >= float64(time.Minute*5)/1000000000 {
		miner.M5SubmitTime = now
		miner.M5Hashrate = miner.M5Accepts * float64(diffOneShareHashesAvg) / m5_duration / 1000000
		//logg.Info("[server]", zap.Float64("Accepts", miner.Accepts), zap.Float64("M5Accepts", miner.M5Accepts), zap.Float64("M5Hashrate(MH/S)", miner.M5Hashrate))
		miner.M5Accepts = 0
	}

	//logg.Warn("[server]", zap.Float64("Accepts", miner.Accepts), zap.Float64("Rejects", miner.Rejects))
	//logg.Info("[server]", zap.Float64("TargetShares", miner.VarDiffOpt.TargetShares), zap.Float64("MinShares", miner.VarDiffOpt.MinShares), zap.Float64("MaxShares", miner.VarDiffOpt.MaxShares), zap.Float64("SubmitShares", miner.VarDiffOpt.SubmitShares))
	//logg.Warn("[server]", zap.Float64("reject rate", miner.Rejects/(miner.Accepts+miner.Rejects)), zap.Float64("Hashrate(MH/S)", miner.AverageHashrate))
	//logg.Warn("[server]", zap.Float64("M5Accepts", miner.M5Accepts), zap.Float64("M5Hashrate(MH/S)", miner.M5Hashrate))
	if miner.ZlogInit {
		miner.Zlog.Info().Msg(miner.User + "." + miner.Miner + " handle_submit M5Accepts " + fmt.Sprintf("%f", (miner.M5Accepts)) + " Hashrate(MH/S) " + fmt.Sprintf("%f", miner.AverageHashrate) + " M5Hashrate(MH/S) " + fmt.Sprintf("%f", miner.M5Hashrate))
	}

	//logg.Info("[server]", zap.Float64("LastCalcTime", float64(now.Sub(miner.VarDiffOpt.LastCalcTime))/1000000000))
	//calc acutal submit shares period of time, then compare with target shares and adjust diff

	if miner.Server.Config.Diff.Filter == "kalman" {
	} else {
		if float64(now.Sub(miner.VarDiffOpt.LastCalcTime))/1000000000 >= miner.VarDiffOpt.AdjustTime {
			coin.VarAdjustDifficulty(miner, coin.UPDATE_DIFF)
			miner.VarDiffOpt.LastCalcTime = now
		}
	}

	if new_found {
		//util.StaleAllJobs(miner)

		/*user_blk_item.User = miner.User
		user_blk_item.Miner = miner.Miner
		user_blk_item.Index = fmt.Sprint(miner.MinerIndex)

		user_blk_item.Submit = "y"
		user_blk_item.Success = false
		user_blk_item.Accepts = miner.Accepts
		user_blk_item.Rejects = miner.Rejects
		user_blk_item.Reward = 0
		user_blk_item.Fee = 0
		user_blk_item.Nonce = nonce
		user_blk_item.SubIdx = miner.Server.SubIdx
		dbif.NotifyUsersBlkStatsDb2(miner, &user_blk_item)*/

		pool_blk_item.Submit = "y"
		pool_blk_item.Success = false
		pool_blk_item.Accepts = miner.Accepts
		pool_blk_item.Rejects = miner.Rejects
		pool_blk_item.Reward = 0
		pool_blk_item.Fee = 0
		pool_blk_item.Nonce = nonce
		pool_blk_item.SubIdx = miner.Server.SubIdx
		dbif.NotifyPoolBlkStatsDb2(miner.Server, &pool_blk_item)

	}

	if ack.Result {

		submit_item.Success = false
		if new_found {
			submit_item.Submit = "y"
			submit_item.SubIdx = miner.Server.SubIdx
		} else {
			submit_item.Submit = "n"
			submit_item.SubIdx = -1
		}

		submit_item.Accepts = miner.Accepts
		submit_item.Total_accepts = miner.Accepts
		submit_item.Rejects = miner.Rejects
		submit_item.Total_rejects = miner.Rejects
		submit_item.Reward = 0
		submit_item.Fee = 0
		submit_item.Nonce = nonce

		//dbif.NotifyMinerDb2(miner, &submit_item)

		dbif.NotifyBlkDetailDb(miner, blk_detail_height, blk_detail_hash, blk_detail_success, blk_detail_miner_diff, blk_detail_pool_diff, nonce, submit_item.SubIdx)
		return true, new_found, true
	}
	return false, false, true
}

func Produce_block_submit(miner *coin.MinerObj, header wire.BlockHeader, job *msg.StratumJob, PowHash string, SubIdx int64) {
	var nm msg.BlockGrsMsg

	buf := bytes.NewBuffer(make([]byte, 0, wire.MaxBlockHeaderPayload))
	err := header.Serialize(buf)
	if err != nil {
		logg.Error("[server]", zap.String("Bytes", err.Error()))
		return
	}
	blk := hex.EncodeToString(buf.Bytes())
	txns := len(*job.TransData) + 1
	var txnss string
	if txns < 0xfd {
		//txnss = hex.EncodeToString([]byte(string(txns)))
		txnsb := make([]byte, 2)
		binary.LittleEndian.PutUint16(txnsb, uint16(txns))
		txnss = hex.EncodeToString(txnsb[:1])
	} else if txns < 0xffff {
		blk += "fd"
		txnsb := make([]byte, 2)
		binary.LittleEndian.PutUint16(txnsb, uint16(txns))
		txnss = hex.EncodeToString(txnsb)
	} else {
		blk += "fe"
		txnsb := make([]byte, 4)
		binary.LittleEndian.PutUint32(txnsb, uint32(txns))
		txnss = hex.EncodeToString(txnsb)
	}
	blk += txnss

	blk += (job.Coinbase1 + job.Extranonce1 + job.Extranonce2 + job.Coinbase2)
	/*blk += job.Coinbase1[:8]
	blk += "0001"
	c2 := len(job.Coinbase2)
	blk += (job.Coinbase1[8:] + job.Extranonce1 + job.Extranonce2 + job.Coinbase2[:c2-8])
	blk += "01200000000000000000000000000000000000000000000000000000000000000000"
	blk += job.Coinbase2[c2-8:]*/

	for i := range *job.TransData {
		blk += (*job.TransData)[i]
	}

	bh, _ := hex.DecodeString(blk)
	nm.Data = bh

	nm.Nonce = job.Nonce
	nm.Pow = PowHash
	nm.SubIdx = SubIdx
	nm.User = miner.User
	nm.Miner = miner.Miner
	nm.Index = fmt.Sprint(miner.MinerIndex)
	body, err := json.Marshal(nm)
	if err != nil {
		logg.Error("[server]", zap.String("failed to Marshal job", err.Error()))
		return
	}
	blk2 := string(body)

	//Add Height
	heightb := utility.Uint32ToByte(job.Height)
	heights := hex.EncodeToString(heightb)
	blk2 += heights
	var Height uint32 = utility.ByteToUint32(heightb)
	logg.Warn("[server]", zap.Uint32("Height", Height))

	//Add SubmitIndex
	indexb := utility.Uint32ToByte(miner.SubmitIndex)
	indexs := hex.EncodeToString(indexb)
	blk2 += indexs
	var SubmitIndex uint32 = utility.ByteToUint32(indexb)
	logg.Info("[server]", zap.Uint32("SubmitIndex", SubmitIndex))
	logg.Info("[server]", zap.String("blk", blk2))

	if miner.Server.PubCh == nil {
		miner.Server.PubCh = utility.InitZmqPub(miner.Server.Config.Zmq.Pub)
	}
	if miner.Server.PubCh != nil {
		blktopic := "blk" + miner.Server.MinerType
		//miner.Server.PubCh.SendChan <- [][]byte{[]byte("blkdgb"), []byte(blk)}
		err := miner.Server.PubCh.SendMessage([][]byte{[]byte(blktopic), []byte(blk2)})
		if err != nil {
			miner.Server.PubCh.Destroy()
			miner.Server.PubCh = nil
			logg.Info("[server]", zap.String("blk", err.Error()))
		} else {
			logg.Info("[server]", zap.String("blk", "sent"))
		}
	}
}

// server-->miner
func dgb_parse_miner_notify(miner *coin.MinerObj, msg msg.StratumJob) int {
	isJobChanged := miner.Job.Height != msg.Height
	extrannonce1 := miner.Job.Extranonce1
	miner.Job = msg
	if isJobChanged {
		miner.Job.IsClean = true
	}
	miner.Job.Extranonce1 = extrannonce1
	miner.Job.Extranonce2_size = msg.Extranonce2_size

	//miner.Server.Logg.Info("[server]", zap.Int32("miner.Version", miner.Version), zap.Int32("msg.Version", msg.Version))
	return 1
}

func Init(server *coin.ServerContext) {
	ServerDgbCtx.ServerCtx = server
	logg = server.Logg
	logg.Info("[server]", zap.String("server_dgb_version", SERVER_DGB_VERSION))
	coin.Init_diff_db()
}

func Start() {

}

func Stop() {
	coin.DiffStop()
}

func InitMiner(miner *coin.MinerObj) {
	be1 := make([]byte, 4)
	binary.LittleEndian.PutUint32(be1, (uint32(miner.Server.Extranonce1) + 0x81000000))
	miner.Job.Extranonce1 = hex.EncodeToString(be1)
	//miner.NexaJob.Extranonce1 = miner.Job.Extranonce1
	miner.Server.Extranonce1++

	target, err := utility.DiffToTarget(miner.Difficulty)
	if err != nil {
		logg.Error("[server]", zap.String("DiffToTarget", err.Error()))
		return
	}
	miner.Target = target
	logg.Debug("[target]", zap.String("target", hex.EncodeToString(target.Bytes())), zap.Float64("diff", miner.Difficulty))

	server_target := new(big.Int)
	t_bytes, err := hex.DecodeString(miner.Job.Target)
	if err != nil {
		logg.Error("[server]", zap.String("DecodeString", err.Error()))
		return
	}
	//server_target.SetBytes(common.Reverse(t_bytes))
	server_target.SetBytes(t_bytes)
	miner.ServerTarget = server_target
	miner.ServerTargetS = miner.Server.SJob.Target
}

func Handle_subscribe_dgb(miner *coin.MinerObj, id float64, extranonce1 string) {
	stratum.Handle_subscribe(miner, id, extranonce1)
}

func HandleMinerSubscribe(miner *coin.MinerObj, id float64, extranonce1 string, msg string) {
	if strings.Contains(msg, "YxMiner") {
		miner.Protocol = "yxminer"
	} else {
		miner.Protocol = "standard"
	}
	Handle_subscribe_dgb(miner, id, extranonce1)
}

func HandleMinerAuth(miner *coin.MinerObj) {

}

func HandleMinerSubmit(miner *coin.MinerObj, id float64, miner_user string, job_id string, nonce2 string, ntime string, nonce string) (bool, bool, bool) {

	accept_ok, submit_ok, handle_ok := handle_submit(miner, id, miner_user, job_id, nonce2, ntime, nonce)
	return accept_ok, submit_ok, handle_ok
}

func set_difficulty(miner *coin.MinerObj) {
	miner.TxLock.Lock()
	var msg stratum.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 = stratum.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 SetDifficulty(miner *coin.MinerObj) {
	set_difficulty(miner)
}

func DgbNotify(miner *coin.MinerObj) {
	stratum.Notify(miner)
}

func Notify(miner *coin.MinerObj) {
	DgbNotify(miner)
}

func HandleJobMsg(server *coin.ServerContext, Msg []byte) {
	var result msg.StratumJob
	server.Logg.Warn("[server]", zap.String("receive", "job"))

	if err := json.Unmarshal(Msg, &result); err != nil {
		server.Logg.Error("[server]", zap.String("Unmarshal", err.Error()))
		return
	}
	server.SJob = msg.StratumJob(result)
	logg.Debug("[gbt]", zap.String("Target", server.SJob.Target))

	logg.Debug("[gbt]", zap.Uint32("Height", server.SJob.Height), zap.String("Target", server.SJob.Target) /*, zap.Uint64("Timastamp", server.DgbJob.CurTime)*/)
	targetb, _ := hex.DecodeString(server.SJob.Target)
	logg.Debug("[gbt]", zap.Float64("network diff", utility.Target2Diff(utility.Reverse(targetb))))

	server.NetHight = uint64(server.SJob.Height)
	server.NetTarget = server.SJob.Target

	server.Miners.Range(func(k, v interface{}) bool {
		m, ok := v.(*(coin.MinerObj))
		if ok {
			//server.Logg.Info("[server]", zap.String("lock", "start"))
			m.TxLock.Lock()
			status := m.Status
			cmd := dgb_parse_miner_notify(m, server.SJob)
			m.TxLock.Unlock()
			//server.Logg.Info("[server]", zap.String("lock", "end"))
			var need_notify bool = true
			if time.Now().Sub(m.ConnSetupTime) >= time.Duration(coin.CONN_EXPIRED_TIME)*time.Second {
				if (status != coin.MINER_STATUS_RUNNING) && (status != coin.MINER_STATUS_AUTHORIZED) {
					//m.Conn.Close()
					need_notify = false
				}
			}
			if need_notify {
				switch cmd {
				case 0: //extranonce 1 and extranonce2 size
					//TODO
				case 1: //notify
					DgbNotify(m)
				}
			}
		}
		return true
	})
}

func IsMhsLow(miner *coin.MinerObj) bool {
	return miner.Mhs5M < 1
}

func GetBlockInterval() int {
	return 90
}
