package client

import (
	"errors"
	"fmt"
	"net"
	"sync"
	"time"
	"virjar.com/majora-go/common"
	"virjar.com/majora-go/log"
	"virjar.com/majora-go/protocol"
	"virjar.com/majora-go/trace"
)

type Transfer struct {
	serialNumber             int64
	client                   *Client
	upstreamConn             *net.TCPConn
	recorder                 trace.Recorder
	transferChan             chan *protocol.MajoraPacket
	transferToUpstreamFunc   func(t *Transfer, p *protocol.MajoraPacket)
	transferToDownstreamFunc func(t *Transfer, data []byte, err error)
	once                     sync.Once
	cancel                   chan struct{}
}

func NewTransfer(serialNumber int64, client *Client, conn *net.TCPConn, recorder trace.Recorder) *Transfer {
	return &Transfer{
		serialNumber: serialNumber,
		client:       client,
		upstreamConn: conn,
		recorder:     recorder,
		transferChan: make(chan *protocol.MajoraPacket, 10),
		once:         sync.Once{},
		cancel:       make(chan struct{}, 0),
	}
}

func (t *Transfer) SetTransferToUpstreamFunc(f func(t *Transfer, p *protocol.MajoraPacket)) {
	t.transferToUpstreamFunc = f
}

func (t *Transfer) SetTransferToDownstreamFunc(f func(t *Transfer, data []byte, err error)) {
	t.transferToDownstreamFunc = f
}

func (t *Transfer) TransferToUpstream(p *protocol.MajoraPacket) {
	t.transferChan <- p
}

func (t *Transfer) Start() {
	if t.transferToUpstreamFunc == nil {
		panic(errors.New("transferToUpstreamFunc is nil"))
	}

	if t.transferToDownstreamFunc == nil {
		panic(errors.New("transferToDownstreamFunc is nil"))
	}

	taskPool.AddTaskAlways(func() {
		for {
			select {
			case p := <-t.transferChan:
				t.transferToUpstreamFunc(t, p)
			case <-t.cancel:
				return
			}
		}
	})

	taskPool.AddTaskAlways(func() {
		traceRecorder := t.recorder
		traceRecorder.RecordEvent(trace.UpStreamEvent, fmt.Sprintf("Ready read from upstream (sn:%d)", t.serialNumber))
		log.Run().Debugf("[handleUpStream] %d-> handleUpStream start...", t.serialNumber)
		for {
			buf := make([]byte, common.BufSize)
			cnt, err := t.upstreamConn.Read(buf)
			t.transferToDownstreamFunc(t, buf[0:cnt], err)
			if err != nil {
				break
			}
		}
	})
}

func (t *Transfer) Close() {
	t.once.Do(func() {
		readDeadLine := time.Now().Add(3 * time.Millisecond)
		t.recorder.RecordEvent(trace.DisconnectEvent, fmt.Sprintf("Set upstream read deadline:%s (sn:%d)",
			readDeadLine.Format("2006-01-02 15:04:05.000000"), t.serialNumber))
		err := t.upstreamConn.SetReadDeadline(readDeadLine)
		if err != nil {
			t.recorder.RecordErrorEvent(trace.DisconnectEvent,
				fmt.Sprintf("Set upstream read deadline failed (sn:%d)", t.serialNumber), err)
			_ = t.upstreamConn.Close()
		}
		close(t.cancel)
	})
}
