package s7

import (
	"context"
	"io"
	"log"
	"sync"
	"time"

	"github.com/robinson/gos7"
)

type DataHandler interface {
	HandleData(client gos7.Client) error
}

type s7Conn struct {
	conn        *gos7.TCPClientHandler
	isConnected bool

	Address string
	Handler DataHandler

	sync.Mutex
	log    log.Logger
	ctx    context.Context
	cancel context.CancelFunc
}

func (c *s7Conn) connect() error {
	c.isConnected = false
	_ = c.conn.Close()
	if err := c.conn.Connect(); err != nil {
		return err
	}
	c.isConnected = true
	return nil
}

func (c *s7Conn) rawConn() gos7.Client {
	return gos7.NewClient(c.conn)
}

func (c *s7Conn) reconnect(ctx context.Context) {
	const idleTime = 5 * time.Second
	t := time.NewTimer(idleTime)
	defer t.Stop()
	for {
		select {
		case <-ctx.Done():
			return
		case <-t.C:
			if !c.isConnected {
				if err := c.connect(); err == nil {
					c.isConnected = true
					c.log.Debug("s7Conn: reconnect ok")
				} else {
					c.log.Error("s7Conn: reconnect err: %s", err)
				}
			}
			t.Reset(idleTime)
		}
	}
}

func (c *s7Conn) serve(ctx context.Context) {
	const idleTime = 500 * time.Millisecond
	t := time.NewTimer(idleTime)
	defer t.Stop()
	for {
		select {
		case <-ctx.Done():
			return
		case <-t.C:
			if c.isConnected {
				c.Lock()
				if err := c.Handler.HandleData(gos7.NewClient(c.conn)); err != nil {
					c.isConnected = false
					c.log.Error("Handler err: %s", err)
				}
				c.Unlock()
			}
			t.Reset(idleTime)
		}
	}
}

func (c *s7Conn) IsConnected() bool {
	return c.isConnected
}

func (c *s7Conn) IsClosed() bool {
	return c.ctx.Err() != nil
}

func (c *s7Conn) Close() error {
	c.cancel()
	return nil
}

func createS7Conn(ctx context.Context, address string, data DataHandler, logs log.Logger) *s7Conn {
	conn := &s7Conn{
		log:     logs,
		Address: address,
		Handler: data,
	}
	conn.ctx, conn.cancel = context.WithCancel(ctx)

	handler := gos7.NewTCPClientHandler(address, 0, 0)
	handler.Timeout = 30 * time.Second
	conn.conn = handler
	rlog.SetOutput(io.Discard) // 禁用原生日志打印

	go conn.reconnect(ctx)
	go conn.serve(ctx)
	return conn
}
