package core
import (
	"fmt"
	"io"
	"io/ioutil"
	"path"
	"path/filepath"
	"plugin"
	hp "github.com/feiin/go-sniffer/plugSrc/http/build"
	mongodb "github.com/feiin/go-sniffer/plugSrc/mongodb/build"
	mssql "github.com/feiin/go-sniffer/plugSrc/mssql/build"
	mysql "github.com/feiin/go-sniffer/plugSrc/mysql/build"
	redis "github.com/feiin/go-sniffer/plugSrc/redis/build"
	"github.com/google/gopacket"
)
type Plug struct {
	dir           string
	ResolveStream func(net gopacket.Flow, transport gopacket.Flow, r io.Reader)
	BPF           string
	InternalPlugList map[string]PlugInterface
	ExternalPlugList map[string]ExternalPlug
}
// All internal plug-ins must implement this interface
// ResolvePacket - entry
// BPFFilter     - set BPF, like: mysql(tcp and port 3306)
// SetFlag       - plug-in params
// Version       - plug-in version
type PlugInterface interface {
	ResolveStream(net gopacket.Flow, transport gopacket.Flow, r io.Reader)
	BPFFilter() string
	SetFlag([]string)
	Version() string
}
type ExternalPlug struct {
	Name          string
	Version       string
	ResolvePacket func(net gopacket.Flow, transport gopacket.Flow, r io.Reader)
	BPFFilter     func() string
	SetFlag       func([]string)
}
func NewPlug() *Plug {
	var p Plug
	p.dir, _ = filepath.Abs("./plug/")
	p.LoadInternalPlugList()
	p.LoadExternalPlugList()
	return &p
}
func (p *Plug) LoadInternalPlugList() {
	list := make(map[string]PlugInterface)
	//Mysql
	list["mysql"] = mysql.NewInstance()
	//Mongodb
	list["mongodb"] = mongodb.NewInstance()
	//Redis
	list["redis"] = redis.NewInstance()
	//Http
	list["http"] = hp.NewInstance()
	list["mssql"] = mssql.NewInstance()
	p.InternalPlugList = list
}
func (p *Plug) LoadExternalPlugList() {
	dir, err := ioutil.ReadDir(p.dir)
	if err != nil {
		return
	}
	p.ExternalPlugList = make(map[string]ExternalPlug)
	for _, fi := range dir {
		if fi.IsDir() || path.Ext(fi.Name()) != ".so" {
			continue
		}
		plug, err := plugin.Open(p.dir + "/" + fi.Name())
		if err != nil {
			panic(err)
		}
		versionFunc, err := plug.Lookup("Version")
		if err != nil {
			panic(err)
		}
		setFlagFunc, err := plug.Lookup("SetFlag")
		if err != nil {
			panic(err)
		}
		BPFFilterFunc, err := plug.Lookup("BPFFilter")
		if err != nil {
			panic(err)
		}
		ResolvePacketFunc, err := plug.Lookup("ResolvePacket")
		if err != nil {
			panic(err)
		}
		version := versionFunc.(func() string)()
		p.ExternalPlugList[fi.Name()] = ExternalPlug{
			ResolvePacket: ResolvePacketFunc.(func(net gopacket.Flow, transport gopacket.Flow, r io.Reader)),
			SetFlag:       setFlagFunc.(func([]string)),
			BPFFilter:     BPFFilterFunc.(func() string),
			Version:       version,
			Name:          fi.Name(),
		}
	}
}
func (p *Plug) ChangePath(dir string) {
	p.dir = dir
}
func (p *Plug) PrintList() {
	//Print Internal Plug
	for inPlugName, _ := range p.InternalPlugList {
		fmt.Println("internal plug : " + inPlugName)
	}
	//split
	fmt.Println("-- --- --")
	//print External Plug
	for exPlugName, _ := range p.ExternalPlugList {
		fmt.Println("external plug : " + exPlugName)
	}
}
func (p *Plug) SetOption(plugName string, plugParams []string) {
	fmt.Println("internalPlug", plugName)
	//Load Internal Plug
	if internalPlug, ok := p.InternalPlugList[plugName]; ok {
		p.ResolveStream = internalPlug.ResolveStream
		internalPlug.SetFlag(plugParams)
		p.BPF = internalPlug.BPFFilter()
		return
	}
	//Load External Plug
	plug, err := plugin.Open("./plug/" + plugName)
	if err != nil {
		panic(err)
	}
	resolvePacket, err := plug.Lookup("ResolvePacket")
	if err != nil {
		panic(err)
	}
	setFlag, err := plug.Lookup("SetFlag")
	if err != nil {
		panic(err)
	}
	BPFFilter, err := plug.Lookup("BPFFilter")
	if err != nil {
		panic(err)
	}
	p.ResolveStream = resolvePacket.(func(net gopacket.Flow, transport gopacket.Flow, r io.Reader))
	setFlag.(func([]string))(plugParams)
	p.BPF = BPFFilter.(func() string)()
}