// Copyright 2021 CloudWeGo Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package protoc

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	gengo "google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo"
	"google.golang.org/protobuf/compiler/protogen"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/types/pluginpb"

	"github.com/cloudwego/kitex/tool/internal_pkg/log"
	"github.com/cloudwego/kitex/tool/internal_pkg/util"
)

// PluginName .
const PluginName = "protoc-gen-kitex"

// Run .
func Run() int {
	opts := protogen.Options{}
	if err := DoRun(opts); err != nil {
		fmt.Fprintf(os.Stderr, "%s: %v\n", filepath.Base(os.Args[0]), err)
		return 1
	}
	return 0
}

func DoRun(opts protogen.Options) error {
	// unmarshal request from stdin
	in, err := util.ReadInput()
	if err != nil {
		return err
	}
	req := &pluginpb.CodeGeneratorRequest{}
	if err := proto.Unmarshal(in, req); err != nil {
		return err
	}

	resp, err := GenKitex(req, opts)
	if err != nil {
		return err
	}
	out, err := proto.Marshal(resp)
	if err != nil {
		return err
	}
	if _, err := os.Stdout.Write(out); err != nil {
		return err
	}
	return nil
}

func GenKitex(req *pluginpb.CodeGeneratorRequest, opts protogen.Options) (*pluginpb.CodeGeneratorResponse, error) {
	// init plugin
	pp := new(protocPlugin)
	pp.init()
	args := strings.Split(req.GetParameter(), ",")
	err := pp.Unpack(args)
	if err != nil {
		return nil, fmt.Errorf("%s: unpack args: %w", PluginName, err)
	}

	pp.parseM()

	// unify go_package
	pe := &pathElements{
		module: pp.ModuleName,
		prefix: pp.PackagePrefix,
	}
	for _, f := range req.ProtoFile {
		if f == nil {
			return nil, errors.New("ERROR: got nil ProtoFile")
		}

		gopkg, ok := pp.importPaths[f.GetName()]
		if ok {
			f.Options.GoPackage = &gopkg
			log.Debugf("option specified import path for %q: %q\n", f.GetName(), gopkg)
		} else {
			if f.Options == nil || f.Options.GoPackage == nil {
				return nil, fmt.Errorf("ERROR: go_package is missing in proto file %q", f.GetName())
			}
			gopkg = f.GetOptions().GetGoPackage()
		}
		if path, ok := pe.getImportPath(gopkg); ok {
			f.Options.GoPackage = &path
			log.Debugf("update import path for %q: %q -> %q\n", f.GetName(), gopkg, path)
		}
	}

	// generate files
	gen, err := opts.New(req)
	if err != nil {
		return nil, err
	}
	pp.process(gen)
	gen.SupportedFeatures = gengo.SupportedFeatures

	// FastPB is deprecated, so truncate all fastpb files
	util.TruncateAllFastPBFiles(filepath.Join(pp.OutputPath, pp.GenPath))
	// construct plugin response
	resp := gen.Response()
	return resp, nil
}
