// package elem_link -- связь между объектами
package elem_link

import (
	"fmt"

	"gitp78su.ipnodns.ru/svi/kern/v3"

	"gitp78su.ipnodns.ru/svi/goarch/lev0/cons"
	"gitp78su.ipnodns.ru/svi/goarch/lev0/types"
)

// ElemLink -- связь между объектами
type ElemLink struct {
	types.IElemBase
	CoordEnd_ types.ICoord    `yaml:"coord_end"` // Координаты второй точки
	Src_      types.IElemBase `yaml:"src"`       // Источник координат
	Dst_      types.IElemBase `yaml:"dst"`       // Получатель координат

	TypeLink_ string `yaml:"typeLink"` // Тип связи
}

var (
	hassert = kern.GetFnHassert()
)

// NewElemLink -- возвращает новую связь
func NewElemLink(elemBase types.IElemBase, coordEnd types.ICoord,
	src, dst types.IElemBase, typeLink string) types.IElemLink {

	sf := &ElemLink{
		IElemBase: elemBase,
		CoordEnd_: coordEnd,
		Src_:      src,
		Dst_:      dst,
		TypeLink_: typeLink,
	}
	return sf
}

// SelfCheck -- самопроверка элемента
func (sf *ElemLink) SelfCheck() {
	sf.IElemBase.SelfCheck()
	hassert(sf.CoordEnd_ != nil, "ElemLink.SelfCheck(): id=%v, CoordEnd_ == nil", sf.Id())
	hassert(sf.Src_ != nil, "ElemLink.SelfCheck(): id=%v, Src_ == nil", sf.Id())
	hassert(sf.Dst_ != nil, "ElemLink.SelfCheck(): id=%v, Dst_ == nil", sf.Id())
	hassert(sf.TypeLink_ != "", "ElemLink.SelfCheck(): id=%v, TypeLink_ is empty", sf.Id())
}

// CoordEnd -- координаты конца связи
//
//go:fix inline
func (sf *ElemLink) CoordEnd() types.ICoord {
	return sf.CoordEnd_
}

// Links -- ссылки актора
func (sf *ElemLink) Links() []types.IElemBase {
	return []types.IElemBase{sf.Src_, sf.Dst_}
}

// Check -- проверяет связи между объектами
func (sf *ElemLink) Check() string {
	if sf.Dst_.Id() == sf.Src_.Id() {
		return fmt.Sprintf("ВНИМАНИЕ! Источник и получатель совпадают\nsrc=%v, dst=%v\n",
			sf.Src_.Id(), sf.Dst_.Id())
	}
	if msgErr := sf.checkSrc(); msgErr != "" {
		return "ОШИБКА при проверке связи источника\n" + msgErr
	}
	if msgErr := sf.checkDst(); msgErr != "" {
		return "ОШИБКА при проверке связи получателя\n" + msgErr
	}
	if sf.TypeLink_ == "" { // Проверка на правильность типа связи
		return "Пустой собственный тип связи\n"
	}
	return ""
}

// проверить связь получателя
func (sf *ElemLink) checkDst() string {
	if sf.Dst_ == nil {
		return "ОШИБКА получатель `dst` не задан"
	}
	// Вычислить на допустимые типы для получателя
	switch sf.Dst_.Type() {
	case cons.TypeUseActor:
		actor, isOk := sf.Dst_.(types.IActor)
		if !isOk {
			return fmt.Sprintf("Недопустимый тип получателя(%q)\nтип=%q\n", sf.Dst_.Id(), sf.Dst_.Type())
		}
		isOk = false
		for _, id := range actor.Links() {
			if id == sf.Id() {
				isOk = true
				break
			}
		}
		if !isOk {
			return fmt.Sprintf("Невзаимная ссылка\nid=%q,dst=(%q)",
				sf.Id(), actor.Id())
		}
	case cons.TypeUseLink:
		link, isOk := sf.Dst_.(types.IElemLink)
		if !isOk {
			return fmt.Sprintf("Недопустимый тип получателя(%q)\nтип=%q\n",
				sf.Dst_.Id(), sf.Dst_.Type())
		}
		isOk = sf.Id() == link.Dst().Id() || sf.Id() == link.Src().Id()
		if !isOk {
			return fmt.Sprintf("id=%q, Невзаимная ссылка(%q)\n", sf.Id(), link.Id())
		}
	case cons.TypeUseCase:
		useCase, isOk := sf.Dst_.(types.IElemUseCase)
		if !isOk {
			return fmt.Sprintf("id=%q, Недопустимый тип получателя(%q)\nтип=%q\n",
				sf.Id(), sf.Dst_.Id(), sf.Dst_.Type())
		}
		isOk = false
		for id := range useCase.Links() {
			if id == sf.Id() {
				isOk = true
				break
			}
		}
		if !isOk {
			return fmt.Sprintf("id=%q, Невзаимная ссылка(%q)\n",
				sf.Id(), useCase.Id())
		}
	default: // Недопустимый источник
		return fmt.Sprintf("id=%q, Недопустимый тип получателя(%q)\nid=%q\n",
			sf.Id(), sf.Dst_.Type(), sf.Dst_.Id())
	}
	return ""
}

// Проверяет связь источника
func (sf *ElemLink) checkSrc() string {
	if sf.Src_ == nil {
		return "ВНИМАНИЕ! Источник связи не задан(src)\n"
	}
	switch sf.Src_.Type() {
	case cons.TypeUseActor:
		isOk := false
		actor := sf.Src_.(types.IActor)
		for _, id := range actor.Links() { // Проверка на взаимность
			if id == sf.Id() {
				isOk = true
				break
			}
		}
		if !isOk {
			return fmt.Sprintf("Невзаимная ссылка с актором %q\n", actor.Id())
		}
	case cons.TypeUseLink:
		link, isOk := sf.Src_.(types.IElemLink)
		if !isOk {
			return fmt.Sprintf("Несоответствие типа источника(%q)\nтип='useLink'\n",
				sf.Src_.Id())
		}
		isOk = sf.Id() == link.Dst().Id() || sf.Id() == link.Src().Id()
		if !isOk {
			return fmt.Sprintf("id=%q, Невзаимная ссылка(%q)\n",
				sf.Id(), link.Id())
		}
	case cons.TypeUseCase:
		useCase, isOk := sf.Src_.(types.IElemUseCase)
		if !isOk {
			return fmt.Sprintf("id=%q, Тип источника(%q) не совпадает с фактическим\nтип='useCase'\n",
				sf.Id(), sf.Src_.Id())
		}
		isOk = false
		for id := range useCase.Links() { // Проверка на взаимность
			if id == sf.Id() {
				isOk = true
				break
			}
		}
		if !isOk {
			return fmt.Sprintf("id=%q, Невзаимная ссылка(%q)\n",
				sf.Id(), useCase.Id())
		}
	default: // Неизвестный источник
		return fmt.Sprintf("id=%q, неизвестный тип источника(%q)<br>id=%q\n",
			sf.Id(), sf.Src_.Type(), sf.Src_.Id())
	}
	return ""
}

// Src -- возвращает источник связи
//
//go:fix inline
func (sf *ElemLink) Src() types.IElemBase {
	return sf.Src_
}

// Dst -- возвращает получателя связи
//
//go:fix inline
func (sf *ElemLink) Dst() types.IElemBase {
	return sf.Dst_
}
