package generator

import (
	"strconv"
	"strings"

	"github.com/bilibili/kratos/tool/protobuf/pkg/generator"
	"github.com/bilibili/kratos/tool/protobuf/pkg/naming"
	"github.com/golang/protobuf/proto"
	"github.com/golang/protobuf/protoc-gen-go/descriptor"
	plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
)

type ecode struct {
	generator.Base
	filesHandled int
}

// EcodeGenerator ecode generator.
func EcodeGenerator() *ecode {
	t := &ecode{}
	return t
}

// Generate ...
func (t *ecode) Generate(in *plugin.CodeGeneratorRequest) *plugin.CodeGeneratorResponse {
	t.Setup(in)

	// Showtime! Generate the response.
	resp := new(plugin.CodeGeneratorResponse)
	for _, f := range t.GenFiles {
		respFile := t.generateForFile(f)
		if respFile != nil {
			resp.File = append(resp.File, respFile)
		}
	}
	return resp
}

func (t *ecode) generateForFile(file *descriptor.FileDescriptorProto) *plugin.CodeGeneratorResponse_File {
	var enums []*descriptor.EnumDescriptorProto
	for _, enum := range file.EnumType {
		if strings.HasSuffix(*enum.Name, "ErrCode") {
			enums = append(enums, enum)
		}
	}
	if len(enums) == 0 {
		return nil
	}
	resp := new(plugin.CodeGeneratorResponse_File)
	t.generateFileHeader(file, t.GenPkgName)
	t.generateImports(file)
	for _, enum := range enums {
		t.generateEcode(file, enum)
	}

	resp.Name = proto.String(naming.GenFileName(file, ".ecode.go"))
	resp.Content = proto.String(t.FormattedOutput())
	t.Output.Reset()

	t.filesHandled++
	return resp
}

func (t *ecode) generateFileHeader(file *descriptor.FileDescriptorProto, pkgName string) {
	t.P("// Code generated by protoc-gen-ecode ", generator.Version, ", DO NOT EDIT.")
	t.P("// source: ", file.GetName())
	t.P()
	if t.filesHandled == 0 {
		comment, err := t.Reg.FileComments(file)
		if err == nil && comment.Leading != "" {
			// doc for the first file
			t.P("/*")
			t.P("Package ", t.GenPkgName, " is a generated ecode package.")
			t.P("This code was generated with kratos/tool/protobuf/protoc-gen-ecode ", generator.Version, ".")
			t.P()
			for _, line := range strings.Split(comment.Leading, "\n") {
				line = strings.TrimPrefix(line, " ")
				// ensure we don't escape from the block comment
				line = strings.Replace(line, "*/", "* /", -1)
				t.P(line)
			}
			t.P()
			t.P("It is generated from these files:")
			for _, f := range t.GenFiles {
				t.P("\t", f.GetName())
			}
			t.P("*/")
		}
	}
	t.P(`package `, pkgName)
	t.P()
}

func (t *ecode) generateImports(file *descriptor.FileDescriptorProto) {
	t.P(`import (`)
	t.P(`	"github.com/bilibili/kratos/pkg/ecode"`)
	t.P(`)`)
	t.P()
	t.P(`// to suppressed 'imported but not used warning'`)
	t.P(`var _ ecode.Codes`)
}

func (t *ecode) generateEcode(file *descriptor.FileDescriptorProto, enum *descriptor.EnumDescriptorProto) {
	t.P("// ", *enum.Name, " ecode")
	t.P("var (")

	for _, item := range enum.Value {
		if *item.Number == 0 {
			continue
		}
		// NOTE: eg: t.P("UserNotExist = New(-404) ")
		t.P(*item.Name, " = ", "ecode.New(", strconv.Itoa(int(*item.Number)), ")")
	}

	t.P(")")
}