// Copyright (C) 2014-2018 Goodrain Co., Ltd.
// RAINBOND, Application Management Platform

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. For any non-GPL usage of Rainbond,
// one or multiple Commercial Licenses authorized by Goodrain Co., Ltd.
// must be obtained first.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package parser

import (
	"fmt"
	"strings"

	dbmodel "github.com/goodrain/rainbond/db/model"

	"github.com/docker/distribution/reference"
	"github.com/goodrain/rainbond/builder/parser/code"
	"github.com/goodrain/rainbond/builder/parser/discovery"
	"github.com/goodrain/rainbond/builder/parser/types"
	"github.com/goodrain/rainbond/builder/sources"
	"github.com/sirupsen/logrus"
	"k8s.io/apimachinery/pkg/api/resource"
)

//ParseError 错误信息
type ParseError struct {
	ErrorType   ParseErrorType `json:"error_type"`
	ErrorInfo   string         `json:"error_info"`
	SolveAdvice string         `json:"solve_advice"`
}

//ParseErrorList 错误列表
type ParseErrorList []ParseError

//ParseErrorType 错误类型
type ParseErrorType string

//FatalError 致命错误
var FatalError ParseErrorType = "FatalError"

//NegligibleError 可忽略错误
var NegligibleError ParseErrorType = "NegligibleError"

//Errorf error create
func Errorf(errtype ParseErrorType, format string, a ...interface{}) ParseError {
	parseError := ParseError{
		ErrorType: errtype,
		ErrorInfo: fmt.Sprintf(format, a...),
	}
	return parseError
}

//ErrorAndSolve error create
func ErrorAndSolve(errtype ParseErrorType, errorInfo, SolveAdvice string) ParseError {
	parseError := ParseError{
		ErrorType:   errtype,
		ErrorInfo:   errorInfo,
		SolveAdvice: SolveAdvice,
	}
	return parseError
}

//SolveAdvice 构建a标签建议
func SolveAdvice(actionType, message string) string {
	return fmt.Sprintf("<a action_type=\"%s\">%s</a>", actionType, message)
}

func (p ParseError) Error() string {
	return fmt.Sprintf("Type:%s Message:%s", p.ErrorType, p.ErrorInfo)
}
func (ps ParseErrorList) Error() string {
	var re string
	for _, p := range ps {
		re += fmt.Sprintf("Type:%s Message:%s\n", p.ErrorType, p.ErrorInfo)
	}
	return re
}

//IsFatalError 是否具有致命错误
func (ps ParseErrorList) IsFatalError() bool {
	for _, p := range ps {
		if p.ErrorType == FatalError {
			return true
		}
	}
	return false
}

//Image 镜像
type Image struct {
	name   reference.Named
	source string
	Name   string `json:"name"`
	Tag    string `json:"tag"`
}

//String -
func (i Image) String() string {
	if i.name == nil {
		return ""
	}
	return i.name.String()
}

//Source return the name before resolution
func (i Image) Source() string {
	return i.source
}

//GetTag get tag
func (i Image) GetTag() string {
	return i.Tag
}

//GetRepostory get repostory
func (i Image) GetRepostory() string {
	if i.name == nil {
		return ""
	}
	return reference.Path(i.name)
}

//GetDomain get image registry domain
func (i Image) GetDomain() string {
	if i.name == nil {
		return ""
	}
	domain := reference.Domain(i.name)
	if domain == "docker.io" {
		domain = "registry-1.docker.io"
	}
	return domain
}

//IsOfficial is official image
func (i Image) IsOfficial() bool {
	domain := reference.Domain(i.name)
	if domain == "docker.io" {
		return true
	}
	return false
}

//GetSimpleName get image name without tag and organizations
func (i Image) GetSimpleName() string {
	if strings.Contains(i.GetRepostory(), "/") {
		return strings.Split(i.GetRepostory(), "/")[1]
	}
	return i.GetRepostory()
}

//Parser 解析器
type Parser interface {
	Parse() ParseErrorList
	GetServiceInfo() []ServiceInfo
	GetImage() Image
}

//Lang 语言类型
type Lang string

//ServiceInfo 智能获取的应用信息
type ServiceInfo struct {
	ID             string         `json:"id,omitempty"`
	Ports          []types.Port   `json:"ports,omitempty"`
	Envs           []types.Env    `json:"envs,omitempty"`
	Volumes        []types.Volume `json:"volumes,omitempty"`
	Image          Image          `json:"image,omitempty"`
	Args           []string       `json:"args,omitempty"`
	DependServices []string       `json:"depends,omitempty"`
	ServiceType    string         `json:"service_type,omitempty"`
	Branchs        []string       `json:"branchs,omitempty"`
	Memory         int            `json:"memory,omitempty"`
	Lang           code.Lang      `json:"language,omitempty"`
	ImageAlias     string         `json:"image_alias,omitempty"`
	//For third party services
	Endpoints []*discovery.Endpoint `json:"endpoints,omitempty"`
	//os type,default linux
	OS        string `json:"os"`
	Name      string `json:"name,omitempty"`  // module name
	Cname     string `json:"cname,omitempty"` // service cname
	Packaging string `json:"packaging,omitempty"`
}

//GetServiceInfo GetServiceInfo
type GetServiceInfo struct {
	UUID   string `json:"uuid"`
	Source string `json:"source"`
}

//GetPortProtocol 获取端口协议
func GetPortProtocol(port int) string {
	if port == 80 {
		return "http"
	}
	if port == 8080 {
		return "http"
	}
	if port == 22 {
		return "tcp"
	}
	if port == 3306 {
		return "mysql"
	}
	if port == 443 {
		return "https"
	}
	if port == 3128 {
		return "http"
	}
	if port == 1080 {
		return "udp"
	}
	if port == 6379 {
		return "tcp"
	}
	if port > 1 && port < 5000 {
		return "tcp"
	}
	return "http"
}

var dbImageKey = []string{
	"mysql", "mariadb", "mongo", "redis", "tidb",
	"zookeeper", "kafka", "mysqldb", "mongodb",
	"memcached", "cockroachdb", "cockroach", "etcd",
	"postgres", "postgresql", "elasticsearch", "consul",
	"percona", "mysql-server", "mysql-cluster",
}

//DetermineDeployType Determine the deployment type
// if image like db image,return stateful type
func DetermineDeployType(imageName Image) string {
	for _, key := range dbImageKey {
		if strings.ToLower(imageName.GetSimpleName()) == key {
			return dbmodel.ServiceTypeStateSingleton.String()
		}
	}
	return dbmodel.ServiceTypeStatelessMultiple.String()
}

//readmemory
//10m 10
//10g 10*1024
//10k 128
//10b 128
func readmemory(s string) int {
	def := 512
	s = strings.ToLower(s)
	// <binarySI>        ::= Ki | Mi | Gi | Ti | Pi | Ei
	isValid := false
	validUnits := map[string]string{
		"gi": "Gi", "mi": "Mi", "ki": "Ki",
	}
	for k, v := range validUnits {
		if strings.Contains(s, k) {
			isValid = true
			s = strings.Replace(s, k, v, 1)
			break
		}
	}
	if !isValid {
		validUnits := map[string]string{
			"g": "Gi", "m": "Mi", "k": "Ki",
		}
		for k, v := range validUnits {
			if strings.Contains(s, k) {
				isValid = true
				s = strings.Replace(s, k, v, 1)
				break
			}
		}
	}
	if !isValid {
		logrus.Warningf("s: %s; invalid unit", s)
		return def
	}
	q, err := resource.ParseQuantity(s)
	if err != nil {
		logrus.Warningf("s: %s; failed to parse quantity: %v", s, err)
		return def
	}
	re, ok := q.AsInt64()
	if !ok {
		logrus.Warningf("failed to int64: %d", re)
		return def
	}
	if re != 0 {
		return int(re) / (1024 * 1024)
	}
	return def
}

//ParseImageName parse image name
func ParseImageName(s string) (i Image) {
	ref, err := reference.ParseAnyReference(s)
	if err != nil {
		logrus.Errorf("image name: %s; parse image failure %s", s, err.Error())
		return i
	}
	name, err := reference.ParseNamed(ref.String())
	if err != nil {
		logrus.Errorf("parse image failure %s", err.Error())
		return i
	}
	i.name = name
	i.Tag = sources.GetTagFromNamedRef(name)
	if strings.Contains(s, ":") {
		i.Name = s[:len(s)-(len(i.Tag)+1)]
	} else {
		i.Name = s
	}
	i.source = s
	return
}
