/*
 * Copyright (c) 2022, Alibaba Group;
 * 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 gen

import (
	"fmt"

	"github.com/alibaba/ioc-golang/aop/common"
	"github.com/alibaba/ioc-golang/logger"

	"github.com/spf13/cobra"
	"sigs.k8s.io/controller-tools/pkg/genall"
	"sigs.k8s.io/controller-tools/pkg/markers"

	"github.com/alibaba/ioc-golang/iocli/gen/generator"

	"github.com/alibaba/ioc-golang"
	"github.com/alibaba/ioc-golang/config"
	_ "github.com/alibaba/ioc-golang/iocli/gen/marker/impls"
	"github.com/alibaba/ioc-golang/iocli/root"
)

var (
	// allGenerators maintains the list of all known generators, giving
	// them names for use on the command line.
	// each turns into a command line option,
	// and has options for output forms.
	allGenerators = map[string]genall.Generator{
		"register": generator.Generator{},
	}

	allOutputRules = map[string]genall.OutputRule{
		"dir":       genall.OutputToDirectory(""),
		"none":      genall.OutputToNothing,
		"stdout":    genall.OutputToStdout,
		"artifacts": genall.OutputArtifacts{},
	}

	// optionsRegistry contains all the marker definitions used to process command line options
	optionsRegistry = &markers.Registry{}
)

func init() {
	for genName, gen := range allGenerators {
		// make the generator options marker itself
		defn := markers.Must(markers.MakeDefinition(genName, markers.DescribesPackage, gen))
		if err := optionsRegistry.Register(defn); err != nil {
			panic(err)
		}
		if helpGiver, hasHelp := gen.(genall.HasHelp); hasHelp {
			if help := helpGiver.Help(); help != nil {
				optionsRegistry.AddHelp(defn, help)
			}
		}

		// make per-generation output rule markers
		for ruleName, rule := range allOutputRules {
			ruleMarker := markers.Must(markers.MakeDefinition(fmt.Sprintf("output:%s:%s", genName, ruleName), markers.DescribesPackage, rule))
			if err := optionsRegistry.Register(ruleMarker); err != nil {
				panic(err)
			}
			if helpGiver, hasHelp := rule.(genall.HasHelp); hasHelp {
				if help := helpGiver.Help(); help != nil {
					optionsRegistry.AddHelp(ruleMarker, help)
				}
			}
		}
	}

	// make "default output" output rule markers
	for ruleName, rule := range allOutputRules {
		ruleMarker := markers.Must(markers.MakeDefinition("output:"+ruleName, markers.DescribesPackage, rule))
		if err := optionsRegistry.Register(ruleMarker); err != nil {
			panic(err)
		}
		if helpGiver, hasHelp := rule.(genall.HasHelp); hasHelp {
			if help := helpGiver.Help(); help != nil {
				optionsRegistry.AddHelp(ruleMarker, help)
			}
		}
	}

	// add in the common options markers
	if err := genall.RegisterOptionsMarkers(optionsRegistry); err != nil {
		panic(err)
	}
}

// noUsageError suppresses usage printing when it occurs
// (since cobra doesn't provide a good way to avoid printing
// out usage in only certain situations).
type noUsageError struct{ error }

var debugFlag bool

func init() {
	genCMD.PersistentFlags().BoolVarP(&debugFlag, "debug", "d", false, "Print debug logs of iocli gen")
	root.Cmd.AddCommand(genCMD)
}

var genCMD = &cobra.Command{
	Use:   "gen",
	Short: "Generate IOC-golang register codes.",
	Long:  "Generate IOC-golang register codes.",
	Example: `Generate IOC-golang register codes.,

	# Run all the generators for a given project
	iocli gen 
`,
	RunE: func(c *cobra.Command, rawOpts []string) error {
		if len(rawOpts) == 0 {
			rawOpts = []string{"register", "paths=./..."}
		}

		if len(rawOpts) == 1 {
			rawOpts = append(rawOpts, "register")
		}

		generator.DebugMode = debugFlag

		// disable ioc-golang internal logs
		logger.Disable()
		// load with aop disable
		if err := ioc.Load(config.AddProperty(common.IOCGolangAOPConfigPrefix+".disable", true)); err != nil {
			return err
		}

		rt, err := genall.FromOptions(optionsRegistry, rawOpts)
		if err != nil {
			return err
		}
		if len(rt.Generators) == 0 {
			return fmt.Errorf("no generators specified")
		}

		if hadErrs := rt.Run(); hadErrs {
			return noUsageError{fmt.Errorf("not all generators ran successfully")}
		}
		return nil
	},
}
