/*
 * Copyright (c) 2019 Huawei Technologies Co., Ltd.
 * A-Tune is licensed under the Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
 * PURPOSE.
 * See the Mulan PSL v2 for more details.
 * Create: 2019-10-29
 */

package module

import (
	"context"
	"fmt"
	"gitee.com/openeuler/A-Tune/common/config"
	"google.golang.org/grpc/peer"
	"net"
	"strings"
	"sync"

	"github.com/urfave/cli"
	"google.golang.org/grpc"
)

// OptServices :the global value to store grpc service
var OptServices = struct {
	sync.RWMutex
	services map[string]*ProfileService
}{}

func init() {
	OptServices.services = make(map[string]*ProfileService)
}

// ProfileService :the service for grpc server
type ProfileService struct {
	Name     string
	Requires []string
	Desc     string
	Path     string
	NewInst  func(ctx *cli.Context, opts ...interface{}) (interface{}, error)
}

func (s *ProfileService) String() string {
	return s.Name
}

// SvrService :every service need to implement SvrService and CliService interface.
// one for server side, the other is for client side
type SvrService interface {
	RegisterServer(*grpc.Server) error
	Healthy(opts ...interface{}) error
}

// CliService :the interface for grpc client
type CliService interface {
	Register() error
	GetCommand() cli.Command
}

// AddService method register the service svc
func AddService(svc *ProfileService) error {
	OptServices.Lock()
	defer OptServices.Unlock()

	if _, existed := OptServices.services[svc.Name]; existed {
		return fmt.Errorf("service existed : %s", svc.Name)
	}

	OptServices.services[svc.Name] = svc

	return nil
}

// WalkServices method callback the fn method
func WalkServices(fn func(nm string, svc *ProfileService) error) error {
	OptServices.Lock()
	defer OptServices.Unlock()

	for name, service := range OptServices.services {
		if err := fn(name, service); err != nil {
			return err
		}
	}

	return nil
}

// CreateInstance method return the instance of service of name
func CreateInstance(name string) (interface{}, error) {
	OptServices.Lock()
	defer OptServices.Unlock()

	svc, existed := OptServices.services[name]
	if !existed {
		return nil, fmt.Errorf("There is not %s service", name)
	}

	instance, err := svc.NewInst(nil)
	if err != nil {
		return nil, err
	}

	return instance, nil
}

// GetServices return the slice of the service name
func GetServices() ([]string, error) {
	OptServices.Lock()
	defer OptServices.Unlock()

	var svc []string
	for name := range OptServices.services {
		svc = append(svc, name)
	}

	return svc, nil
}

//CheckRpcIsLocalAddr return whether rpc is a local addr
func CheckRpcIsLocalAddr(ctx context.Context) (bool, error) {
	if config.TransProtocol != "tcp" {
		return true, nil
	}

	pr, ok := peer.FromContext(ctx)
	if !ok || pr.Addr == net.Addr(nil) {
		return false, fmt.Errorf("failed to get rpc client ip")
	}
	clientAddr := strings.Split(pr.Addr.String(), ":")[0]

	serverAddr, err := net.InterfaceAddrs()
	if err != nil {
		return false, err
	}

	for i := range serverAddr {
		addr, _, err := net.ParseCIDR(serverAddr[i].String())
		if err != nil {
			return false, err
		}
		if net.ParseIP(clientAddr).Equal(addr) {
			return true, nil
		}
	}
	return false, nil
}