// package mod_http -- http-модуль для архитектуры
package mod_http

import (
	"bytes"
	_ "embed"
	"encoding/json"
	"fmt"
	"time"

	svg "github.com/ajstarks/svgo"
	"github.com/gofiber/fiber/v2"
	"gopkg.in/yaml.v3"

	"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/lev1/coord"
	"gitp78su.ipnodns.ru/svi/goarch/lev1/size"
	"gitp78su.ipnodns.ru/svi/goarch/lev1/view_use_case"
	"gitp78su.ipnodns.ru/svi/goarch/pkg/elems/canvas"
	"gitp78su.ipnodns.ru/svi/goarch/pkg/views/view_actor"
	"gitp78su.ipnodns.ru/svi/goarch/pkg/views/view_link"
	"gitp78su.ipnodns.ru/svi/goarch/pkg/views/view_use_group"
	"gitp78su.ipnodns.ru/svi/kern/v3"
	"gitp78su.ipnodns.ru/svi/kern/v3/kc/log_buf"
	"gitp78su.ipnodns.ru/svi/kern/v3/krn/ktypes"
)

//go:embed index.html
var indexHtml []byte

// CanvasSize -- размер хоста
type CanvasSize struct {
	SizeX_ int `form:"w"`
	SizeY_ int `form:"h"`
}

func (sf *CanvasSize) String() string {
	return fmt.Sprintf("w:%v; h:%v", sf.SizeX_, sf.SizeY_)
}

// ModHttp -- http-модуль для архитектуры
type ModHttp struct {
	// fibApp     *fiber.App
	kCtx       ktypes.IKernelCtx
	log        ktypes.ILogBuf
	diaMode    string       // Режим диаграммы
	oldBinArch []byte       // Кеш старой архитектуры
	canvSize   CanvasSize   // Размер холста
	size       types.ISize  // Размер холста в его координатах
	pos        types.ICoord // Координаты холста
}

// NewModHttp -- возвращает новый сервис
func NewModHttp() *ModHttp {
	optTerm := log_buf.OptIsTerm(true)
	optPref := log_buf.OptPrefix("ModHttp")
	log := log_buf.NewLogBuf(optTerm, optPref)
	sf := &ModHttp{
		kCtx:    kern.GetKernelCtx(),
		log:     log,
		diaMode: cons.ModeUseCase,
		size:    size.NewSize(800, 600),
		pos:     coord.NewCoord(0, 0),
	}
	sHttp := kern.GetKernelServerHttp()
	fibApp := sHttp.Fiber()
	fibApp.Static("/static", "./static", fiber.Static{
		Compress:      true,
		Browse:        true,
		CacheDuration: time.Second * 10,
	})
	fibApp.Get("/", sf.getIndex)
	fibApp.Get("/api/use_case", sf.postSetModeUseCase)
	fibApp.Post("/api/arch", sf.postArch)
	fibApp.Post("/api/canvas_size", sf.postSetCanvasSize)
	return sf
}

// FormArch -- извлекает данные из текстового поля для обновления
type FormArch struct {
	StrData string `form:"text"`
}

// Устанавливает размер холста
func (sf *ModHttp) postSetCanvasSize(ctx *fiber.Ctx) error {
	err := ctx.BodyParser(&sf.canvSize)
	if err != nil {
		sf.log.Err("postSetCanvasSize(): on body parser, err=\n\t%v", err)
	}
	var (
		w alias.SizeX
		h alias.SizeY
	)
	if sf.canvSize.SizeX_ > 0 {
		w = alias.SizeX(sf.canvSize.SizeX_ - 10)
	}
	if sf.canvSize.SizeY_ > 0 {
		h = alias.SizeY(sf.canvSize.SizeY_ - 10)
	}
	sf.size = size.NewSize(w, h)
	return ctx.SendString(sf.size.String())
}

// Возвращает новый холст
func (sf *ModHttp) canvas(wr *bytes.Buffer, lstElem []map[string]interface{}) (*svg.SVG, error) {
	var (
		elem         map[string]interface{}
		isFind, isOk bool
		_section     interface{}
	)
	for _, elem = range lstElem {
		_section, isOk = elem["type"]
		if !isOk {
			continue
		}
		section, isOk := _section.(string)
		if !isOk {
			return nil, fmt.Errorf("ServHttp.canvas(): in mapElem, type(%#+v) not string<br>%#+v", _section, elem)
		}
		if section == cons.SectionConfig {
			isFind = true
			break
		}
	}
	if !isFind {
		return nil, fmt.Errorf("ServHttp.canvas(): in mapElem, config not found")
	}
	confCanvas := canvas.NewCanvas(sf.diaMode, sf.size, sf.pos)
	sizeX, sizeY := confCanvas.Size().Int()
	canvas := svg.New(wr)
	canvas.Start(sizeX, sizeY)
	confCanvas.Draw(canvas)
	return canvas, nil
}

// По запросу парсит, что получилось -- обновляет архитектуру
func (sf *ModHttp) postArch(ctx *fiber.Ctx) error {
	form := &FormArch{}
	if err := ctx.BodyParser(form); err != nil {
		return ctx.SendString(fmt.Sprintf("ServHttp.archPost(): in BodyParser, err=<br>%v", err))
	}
	binData := []byte(form.StrData)
	if len(binData) == len(sf.oldBinArch) {
		return ctx.Send(sf.oldBinArch)
	}
	mapElem := []map[string]interface{}{}
	err := yaml.Unmarshal(binData, &mapElem)
	if err != nil {
		return ctx.SendString(fmt.Sprintf("ServHttp.archPost(): in Decode JSON, err=<br>%v", err))
	}
	wr := bytes.NewBuffer(nil)
	canvas, err := sf.canvas(wr, mapElem)
	if err != nil {
		return ctx.SendString(fmt.Sprintf("ServHttp.archPost(): in create canvas, err=<br>%v", err))
	}
	switch sf.diaMode {
	case cons.ModeUseCase: // режим вариантов использования
		if err := sf.useCase(canvas, mapElem); err != nil {
			return ctx.SendString(fmt.Sprintf("ServHttp.archPost(): in useCase, err=<br>%v", err))
		}
	default: // неизвестный режим
		return ctx.SendString(fmt.Sprintf("ServHttp.archPost(): unknown mode=%q", sf.diaMode))
	}

	canvas.End()
	binData = wr.Bytes()
	return ctx.Send(binData)
}

// Формирует варианты использования
func (sf *ModHttp) useCase(canvas *svg.SVG, lstElem []map[string]interface{}) error {
	mapDraw := make(map[alias.Id]types.IDrawer)
	for _, elem := range lstElem {
		_id, isOk := elem["id"]
		if !isOk {
			return fmt.Errorf("ServHttp.useCase(): field 'id' not found<br>%#+v", elem)
		}
		id0, isOk := _id.(string)
		if !isOk {
			return fmt.Errorf("ServHttp.useCase(): field 'id'(%T, %+v) not alias.Id", _id, _id)
		}
		id := alias.Id(id0)
		if id == "" {
			return fmt.Errorf("ServHttp.useCase(): id=%q, elem is empty<br>%#+v", id, elem)
		}
		_, isOk = mapDraw[id]
		if isOk {
			return fmt.Errorf("ServHttp.useCase(): id=%q already exists", id)
		}
		binElem, _ := json.MarshalIndent(elem, "", " ")
		_type, isOk := elem["type"]
		if !isOk {
			return fmt.Errorf("ServHttp.useCase(): id=%q, field 'type' not found<br>%+v", id, string(binElem))
		}
		strType, isOk := _type.(string)
		if !isOk {
			return fmt.Errorf("ServHttp.useCase(): id=%q, field 'type'(%+v) not string<br>%+v", id, _type, string(binElem))
		}
		var (
			drawer types.IDrawer
		)
		switch strType {
		case cons.TypeUseActor: // Нарисовать актора
			view_actor.NewViewActor(actor, elem)
		case cons.TypeUseCase: // Нарисовать вариант использования
			drawer = view_use_case.NewViewUseCase(useCase, elem)
		case cons.TypeUseLink: // Нарисовать связь
			drawer = view_link.NewViewLink(useLink)
		case cons.TypeUseGroup: // Нарисовать группу вариантов использования
			drawer = view_use_group.NewViewUseGroup(useGroup, elem)
		}
		if drawer != nil {
			mapDraw[id] = drawer
			sf.log.Debug("useCase(): id=%q, elem=\n%v\n", id, elem)
		}
	}
	for _, drawer := range mapDraw {
		switch drawer.(type) {
		case types.IElemLink:
		}
		drawer.Draw(canvas, mapDraw)
	}
	return nil
}

// Возвращает картинку архитектуры
func (sf *ModHttp) postSetModeUseCase(ctx *fiber.Ctx) error {
	sf.diaMode = cons.ModeUseCase
	return ctx.SendString("[mode='" + sf.diaMode + "']")
}

// Возвращает главную страницу сервиса
func (sf *ModHttp) getIndex(ctx *fiber.Ctx) error {
	ctx.Response().Header.Set("Content-Type", "text/html")
	return ctx.Send(indexHtml)
}
