package main

import (
	"fmt"
	"os"
	"path/filepath"
	"sync"

	protoparser "github.com/yoheimuta/go-protoparser/v4"
	parser "github.com/yoheimuta/go-protoparser/v4/parser"
)

type Generator struct {
	// source paths
	ipaths []string
	// output dir
	output string
}

type Out struct {
	// source file path
	sourcePath string
	// if any err
	err error
	// apis
	apis *map[string]map[Part]*ApiPart
	// segments
	segments *map[string]*Segment
}

func (g *Generator) generateTS() {
	a := make(map[string]map[Part]*ApiPart)
	apis := &a

	s := make(map[string]*Segment)
	segments := &s

	// parse stage
	if ok := g.parse(apis, segments); !ok {
		fmt.Fprintln(os.Stderr, "Abort because an error occurred during parsing.")
		os.Exit(1)
	}

	// compile stage
	compiler := &Compiler{
		apis:     apis,
		segments: segments,
	}
	outs, err := compiler.compile()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Abort because an error occurred during compile, err: %v\n", err)
		os.Exit(1)
	}

	// write files
	for _, out := range outs {
		if err := g.writeFile(out.filename, out.content); err != nil {
			fmt.Fprintf(os.Stderr, "Abort because an error occurred during writing file: %s, err: %v\n", out.filename, err)
			os.Exit(1)
		}
	}

	fmt.Println("\n== Write Done ====>")
	fmt.Printf("Total: %d\n", len(outs))
	for _, out := range outs {
		fmt.Println(out.filename)
	}
	fmt.Println("\n== 🎉 Congratulations, the generating succeeded. 🎉 ===")
}

func (g *Generator) parse(apis *map[string]map[Part]*ApiPart, segments *map[string]*Segment) bool {
	outs := make(chan *Out)
	var wg sync.WaitGroup

	for _, ipath := range g.ipaths {
		wg.Add(1)
		p := filepath.Join(ipath)
		go func(p string) {
			defer wg.Done()

			output := &Out{
				sourcePath: p,
			}

			// parse
			parsed, err := g.parseSingle(p)
			if err != nil {
				output.err = err
				outs <- output
				return
			}

			t := Transformer{}

			apis, segments, err := t.transform("", "", parsed.ProtoBody)
			if err != nil {
				output.err = err
				outs <- output
				return
			}

			output.apis = apis
			output.segments = segments

			outs <- output
		}(p)
	}

	go func() {
		wg.Wait()
		close(outs)
	}()

	var succCount, failCount int
	var finished []string
	for out := range outs {
		if out.err != nil {
			failCount++
			fmt.Fprintf(os.Stderr, "failed to generate from: %s, err %v\n", out.sourcePath, out.err)
			continue
		}
		succCount++
		finished = append(finished, out.sourcePath)
		// merge
		mergeApis(apis, out.apis)
		mergeSegments(segments, out.segments)
	}

	fmt.Println("\n== Parse Done ====>")
	fmt.Printf("Success: %d, Fail: %d\n", succCount, failCount)
	if failCount > 0 {
		return false
	}
	for _, f := range finished {
		fmt.Println(f)
	}

	return true
}

func (g *Generator) parseSingle(pfile string) (*parser.Proto, error) {
	reader, err := os.Open(pfile)
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to open: %s, err %v\n", pfile, err)
		return nil, err
	}

	got, err := protoparser.Parse(reader, protoparser.WithBodyIncludingComments((true)))
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to parse: %s, err %v\n", pfile, err)
		return nil, err
	}

	return got, nil
}

func (g *Generator) writeFile(name string, content []rune) error {
	fPath := filepath.Join(g.output, name)
	out, err := os.OpenFile(fPath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0766)
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to create the file, err: %v\n", err)
		return err
	}
	defer out.Close()

	_, err = out.WriteString(string(content))
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to write the file, err: %v\n", err)
		return err
	}

	return nil
}
