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

import (
	"fmt"

	"gitp78su.ipnodns.ru/svi/goarch/lev0/alias"
	"gitp78su.ipnodns.ru/svi/goarch/lev0/cons"
	"gitp78su.ipnodns.ru/svi/goarch/lev0/types"
	"gitp78su.ipnodns.ru/svi/goarch/pkg/elems/coord"
	"gitp78su.ipnodns.ru/svi/goarch/pkg/elems/elem_base"
)

// UseLink -- связь между объектами
type UseLink struct {
	*elem_base.ElemBase
	CoordEnd_ *coord.Coord `json:"coord1"` // Координаты второй точки
	Src_      alias.Id     `json:"src"`    // Источник координат
	Dst_      alias.Id     `json:"dst"`    // Получатель координат

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

// NewUseLink -- возвращает новую связь
func NewUseLink(elem map[string]interface{}) (*UseLink, error) {
	elemBase, err := elem_base.NewElemBase(elem)
	if err != nil {
		return nil, fmt.Errorf("NewUseLink(): in create BaseSvg, err=\n\t%w", err)
	}
	id := elemBase.Id_
	_src := elemBase.Elem_["src"]
	src0, isOk := _src.(string)
	if !isOk {
		return nil, fmt.Errorf("NewUseLink(): id=%q, field `src` not string, type=%T, value=%v", id, _src, elemBase.StrElem_)
	}
	src := alias.Id(src0)
	if src == "" {
		return nil, fmt.Errorf("NewUseLink(): id=%q, `src` is empty", id)
	}
	_dst := elemBase.Elem_["dst"]
	dst0, isOk := _dst.(string)
	if !isOk {
		return nil, fmt.Errorf("NewUseLink(): id=%q, field `dst` not string, type=%T, value=%+v<br>%+v", id, _dst, _dst, elemBase.StrElem_)
	}
	dst := alias.Id(dst0)
	_typeLink, isOk := elemBase.Elem_["type_link"]
	if !isOk {
		return nil, fmt.Errorf("NewUseLink(): name=%q, field `type_link` not found<br>%+v", id, elemBase.StrElem_)
	}
	typeLink, isOk := _typeLink.(string)
	if !isOk {
		return nil, fmt.Errorf("NewUseLink(): name=%q, `type_link`(%+v) not string", id, _typeLink)
	}
	if typeLink == "" {
		return nil, fmt.Errorf("NewUseLink(): name=%q, type_link=%q is empty", id, typeLink)
	}
	sf := &UseLink{
		ElemBase:  elemBase,
		CoordEnd_: coord.NewCoord("coord1", elem),
		Src_:      src,
		Dst_:      dst,
		TypeLink_: typeLink,
	}
	return sf, nil
}

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

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

// проверить связь получателя
func (sf *UseLink) checkDst(mapDrawer map[alias.Id]types.IElemDrawer) string {
	if sf.Dst_ == "" {
		return "ОШИБКА получатель `dst` не задан"
	}
	dst, isOk := mapDrawer[sf.Dst_] // Вычислить, если такой получатель
	if !isOk {
		return "Отсутствует собственный получатель(dst)\n"
	}
	// Вычислить на допустимые типы для получателя
	switch dst.Type() {
	case cons.TypeUseActor:
		actor, isOk := dst.(types.IActor)
		if !isOk {
			return fmt.Sprintf("Недопустимый тип получателя(%q)\nтип=%q\n", dst.Id(), 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 := dst.(types.ILinker)
		if !isOk {
			return fmt.Sprintf("Недопустимый тип получателя(%q)\nтип=%q\n", dst.Id(), dst.Type())
		}
		isOk = sf.Id_ == link.DstId() || sf.Id_ == link.SrcId()
		if !isOk {
			return fmt.Sprintf("id=%q, Невзаимная ссылка(%q)\n", sf.Id_, link.Id())
		}
	case cons.TypeUseCase:
		useCase, isOk := dst.(types.IUseCase)
		if !isOk {
			return fmt.Sprintf("id=%q, Недопустимый тип получателя(%q)\nтип=%q\n", sf.Id_, dst.Id(), 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_, dst.Type(), dst.Id())
	}
	return ""
}

// Проверяет связь источника
func (sf *UseLink) checkSrc(mapDrawer map[alias.Id]types.IElemDrawer) string {
	if sf.Src_ == "" {
		return "ВНИМАНИЕ! Источник связи не задан(src)\n"
	}
	src, isOk := mapDrawer[sf.Src_]
	if !isOk {
		return "Отсутствует источник(src)\n"
	}
	switch src.Type() {
	case cons.TypeUseActor:
		isOk = false
		actor := types.IActor(src)
		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 := src.(types.ILinker)
		if !isOk {
			return fmt.Sprintf("Несоответствие типа источника(%q)\nтип='useLink'\n", src.Id())
		}
		isOk = sf.Id_ == link.DstId() || sf.Id_ == link.SrcId()
		if !isOk {
			return fmt.Sprintf("id=%q, Невзаимная ссылка(%q)\n", sf.Id_, link.Id())
		}
	case cons.TypeUseCase:
		useCase, isOk := src.(types.IUseCase)
		if !isOk {
			return fmt.Sprintf("id=%q, Тип источника(%q) не совпадает с фактическим\nтип='useCase'\n", sf.Id_, 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_, src.Type(), src.Id())
	}
	return ""
}

// SrcId -- возвращает источник связи
func (sf *UseLink) SrcId() alias.Id {
	return sf.Src_
}

// DstId -- возвращает получателя связи
func (sf *UseLink) DstId() alias.Id {
	return sf.Dst_
}
