package batch

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"xyj-figures/pkg/entity"
	"xyj-figures/pkg/relationship"

	findfont "github.com/flopp/go-findfont"
	graphviz "github.com/goccy/go-graphviz"
	"github.com/goccy/go-graphviz/cgraph"
	"github.com/golang/freetype/truetype"
	log "github.com/sirupsen/logrus"
	"golang.org/x/image/font"
)

const (
	CLUSTER_PREFIX = "cluster_"
)

func LoadFolder(path string) ([]entity.Entity, []relationship.Relationship, error) {
	es := []entity.Entity{}
	rs := []relationship.Relationship{}
	errs := []string{}

	// Triverse all files in path and load all entities and relationships
	err := filepath.Walk(path, func(subpath string, info os.FileInfo, err error) error {
		if err != nil {
			errs = append(errs, err.Error())
			return nil
		}
		if !info.IsDir() {
			e, err := entity.Load(subpath)
			if err != nil {
				errs = append(errs, err.Error())
				return nil
			}
			es = append(es, *e)
			rs = append(rs, e.Details.(entity.EntityDetails).GetRelationships()...)
		}
		return nil
	})
	if len(errs) > 0 {
		return es, rs, fmt.Errorf("multiple errors: %v", strings.Join(errs, "; "))
	}
	return es, rs, err
}

// DistillData takes loaded data and do all needed optimizations and extract only needed info for maps
func DistillData(es []entity.Entity, rs []relationship.Relationship) ([]entity.Entity, []relationship.Relationship, error) {
	// Create a set of all entities, including characters and every members
	// TODO: Duplicated name
	nodes := map[string]entity.Entity{}
	edges := map[string]relationship.Relationship{}
	for _, e := range es {
		switch e.Type {
		case entity.EntityTypeCharacter:
			if val, ok := nodes[e.Details.(entity.Character).Name]; ok {
				if err := val.Merge(e); err != nil {
					log.Warnf("failed to merge: %v", err)
				}
				continue
			}
			nodes[e.Details.(entity.Character).Name] = e
		case entity.EntityTypeGroup:
			nodes[e.Details.(entity.Group).Name] = e
			for _, m := range e.Details.(entity.Group).Members {
				nodes[m] = entity.Entity{
					Schema: "v1",
					Type:   entity.EntityTypeCharacter,
					Details: entity.Character{
						Name: m,
					},
				}
			}
		}
	}

	for _, r := range rs {
		key1, key2 := r.GetKey()
		val1, ok1 := edges[key1]
		val2, ok2 := edges[key2]
		// Both keys don't exist
		if !ok1 && !ok2 {
			edges[key1] = r
			continue
		}
		// Logs all unexpected edges
		if ok1 && !r.IsSame(val1) {
			log.Warningf("Found ambigious edges, existing: %+v, new: %+v", val1, r)
		}
		if ok2 && !r.IsSame(val2) {
			log.Warningf("Found ambigious edges, existing: %+v, new: %+v", val2, r)
		}
	}

	retns := make([]entity.Entity, 0, len(nodes))
	for _, n := range nodes {
		retns = append(retns, n)
	}
	retes := make([]relationship.Relationship, 0, len(edges))
	for _, v := range edges {
		retes = append(retes, v)
	}

	return retns, retes, nil
}

// Save distilled info into DOT type files
func Save(path string, ents []entity.Entity, rels []relationship.Relationship) error {
	g := graphviz.New()
	g.SetLayout(graphviz.TWOPI)
	useSystemFont(g, "SimSum.ttc")
	rootgraph, err := g.Graph()
	rootgraph.SafeSet("fontname", "NSimSun", "Times-Roman")
	if err != nil {
		return fmt.Errorf("failed to create graph: %v", err)
	}
	defer func() {
		if err := rootgraph.Close(); err != nil {
			log.Errorf("failed to close graph: %v", err)
		}
		g.Close()
	}()

	nodesPtr := map[string]*cgraph.Node{}
	subGraphPtr := map[string]*cgraph.Graph{}
	nodeGraphMap := map[string]string{}
	for _, e := range ents {
		if e.Type == entity.EntityTypeGroup {
			subGraph := rootgraph.SubGraph(CLUSTER_PREFIX+e.Details.(entity.Group).Name, 1)
			subGraph.SetLabel(e.Details.(entity.Group).Name)
			// subGraph.SafeSet("fontname", "NSimSun", "Times-Roman")
			subGraphPtr[e.Details.(entity.Group).Name] = subGraph
			// Record all members
			for _, m := range e.Details.(entity.Group).Members {
				nodeGraphMap[m] = e.Details.(entity.Group).Name
			}
		}
	}
	fmt.Println(subGraphPtr)
	fmt.Println(nodeGraphMap)

	for _, e := range ents {
		if e.Type == entity.EntityTypeCharacter {
			log.Debugf("entity: %+v", e)
			if group, ok := nodeGraphMap[e.Details.(entity.Character).Name]; ok {
				ptr, ok := subGraphPtr[group]
				if !ok {
					log.Debugf("can't find sub graph %s", group)
					continue
				}
				if ptr == nil {
					log.Debugf("sub graph %s is nil", group)
					continue
				}
				n, err := ptr.CreateNode(e.Details.(entity.Character).Name)
				if err != nil {
					log.Errorf("failed to create node in sub graph %s: %v", group, err)
					continue
				}
				nodesPtr[e.Details.(entity.Character).Name] = n
				continue
			}
			node, err := rootgraph.CreateNode(e.Details.(entity.Character).Name)
			if err != nil {
				log.Errorf("failed to create node: %v", err)
				continue
			}
			nodesPtr[e.Details.(entity.Character).Name] = node
			continue
		}
	}
	fmt.Println(nodesPtr)

	for _, r := range rels {
		key, _ := r.GetKey()
		srcPtr := nodesPtr[r.Src]
		dstPtr := nodesPtr[r.Dst]
		fmt.Printf("src: %s, dst: %s\n", r.Src, r.Dst)
		e, err := rootgraph.CreateEdge(key, srcPtr, dstPtr)
		if err != nil {
			log.Errorf("failed to create edge: %v", err)
			continue
		}
		e.SetLabel(r.Description)
		e.SafeSet("fontname", "NSimSun", "Times-Roman")
		e.SafeSet("fontsize", "12", "10")
		if r.Bidirection {
			e.SetDir(cgraph.NoneDir)
		}
	}
	if err := g.RenderFilename(rootgraph, "dot", path); err != nil {
		log.Fatal(err)
	}

	if err := g.RenderFilename(rootgraph, graphviz.PNG, path+".png"); err != nil {
		return fmt.Errorf("failed to save graph to disk: %v", err)
	}
	return nil
}

func useSystemFont(g *graphviz.Graphviz, name string) {
	fontPath, err := findfont.Find(name)
	if err != nil {
		panic(err)
	}

	fontData, err := os.ReadFile(fontPath)
	if err != nil {
		panic(err)
	}
	ft, err := truetype.Parse(fontData)
	if err != nil {
		panic(err)
	}
	g.SetFontFace(func(size float64) (font.Face, error) {
		opt := &truetype.Options{
			Size: size,
		}
		return truetype.NewFace(ft, opt), nil
	})
}
