package opt

import (
	"gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2"
	"gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2/ops2"
	"gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2/parser/state"
	"gitlink.org.cn/cloudream/jcs-pub/common/pkgs/storage/factory"
	"gitlink.org.cn/cloudream/jcs-pub/common/pkgs/storage/types"
)

// 将直接从一个存储服务传到另一个存储服务的过程换成S2S传输
func UseS2STransfer(ctx *state.GenerateState) {
	// S2S传输暂不支持只传文件的一部分
	if ctx.StreamRange.Offset != 0 || ctx.StreamRange.Length != nil {
		return
	}

	for fr, frNode := range ctx.FromNodes {
		switch fr := fr.(type) {
		case *ioswitch2.FromShardstore:
			s2sFromShardStore(ctx, fr, frNode)
		case *ioswitch2.FromPublicStore:
			s2sFromPublicStore(ctx, fr, frNode)
		}
	}
}

func s2sFromShardStore(ctx *state.GenerateState, fromShard *ioswitch2.FromShardstore, frNode ops2.FromNode) {
	fromStgBld := factory.GetBuilder(&fromShard.UserSpace)
	if !fromStgBld.FeatureDesc().HasBypassShardRead {
		return
	}

	s2s, err := fromStgBld.CreateS2STransfer()
	if err != nil {
		return
	}

	// 此输出流的所有目的地都要能支持S2S传输
	outVar := frNode.Output().Var()
	if outVar.Dst.Len() == 0 {
		return
	}

	failed := false
	var toShards []*ops2.ShardWriteNode
	var toPublics []*ops2.PublicWriteNode

loop:
	for i := 0; i < outVar.Dst.Len(); i++ {
		dstNode := outVar.Dst.Get(i)

		switch dstNode := dstNode.(type) {
		case *ops2.ShardWriteNode:
			dstStgBld := factory.GetBuilder(&dstNode.UserSpace)
			if !dstStgBld.FeatureDesc().HasBypassShardWrite {
				failed = true
				break
			}

			if !s2s.CanTransfer(&dstNode.UserSpace) {
				failed = true
				break
			}

			toShards = append(toShards, dstNode)

		case *ops2.PublicWriteNode:
			dstStgBld := factory.GetBuilder(&dstNode.UserSpace)
			if !dstStgBld.FeatureDesc().HasBypassPublicWrite {
				failed = true
				break
			}

			if !s2s.CanTransfer(&dstNode.UserSpace) {
				failed = true
				break
			}

			toPublics = append(toPublics, dstNode)

		default:
			failed = true
			break loop
		}
	}
	if failed {
		return
	}

	for _, toShard := range toShards {
		s2sNode := ctx.DAG.NewS2STransfer(fromShard.UserSpace, toShard.UserSpace, types.S2SOption{})
		// 直传指令在目的地Hub上执行
		s2sNode.Env().CopyFrom(toShard.Env())

		// 先获取文件路径，送到S2S节点
		brNode := ctx.DAG.NewBypassFromShardStore(fromShard.UserSpace, fromShard.FileHash)
		brNode.Env().CopyFrom(frNode.Env())
		brNode.FilePathVar().ToSlot(s2sNode.SrcPathSlot())

		// 传输结果通知目的节点
		bwNode := ctx.DAG.NewBypassToShardStore(toShard.UserSpace, toShard.To.FileHashStoreKey)
		bwNode.Env().CopyFrom(toShard.Env())

		s2sNode.BypassFileInfoVar().ToSlot(bwNode.BypassFileInfoSlot())
		bwNode.BypassCallbackVar().ToSlot(s2sNode.BypassCallbackSlot())

		// 从计划中删除目标节点
		ctx.DAG.RemoveNode(toShard)
		delete(ctx.ToNodes, toShard.To)
	}

	for _, toPub := range toPublics {
		s2sNode := ctx.DAG.NewS2STransfer(fromShard.UserSpace, toPub.UserSpace, types.S2SOption{
			DestPathHint: toPub.ObjectPath,
		})
		// 直传指令在目的地Hub上执行
		s2sNode.Env().CopyFrom(toPub.Env())

		// 先获取文件路径，送到S2S节点
		brNode := ctx.DAG.NewBypassFromShardStore(fromShard.UserSpace, fromShard.FileHash)
		brNode.Env().CopyFrom(toPub.Env())
		brNode.FilePathVar().ToSlot(s2sNode.SrcPathSlot())

		// 传输结果通知目的节点
		bwNode := ctx.DAG.NewBypassToPublicStore(toPub.UserSpace, toPub.ObjectPath)
		bwNode.Env().CopyFrom(toPub.Env())

		s2sNode.BypassFileInfoVar().ToSlot(bwNode.BypassFileInfoSlot())
		bwNode.BypassCallbackVar().ToSlot(s2sNode.BypassCallbackSlot())

		// 从计划中删除目标节点
		ctx.DAG.RemoveNode(toPub)
		delete(ctx.ToNodes, toPub.To)
	}

	// 从计划中删除源节点
	ctx.DAG.RemoveNode(frNode)
	delete(ctx.FromNodes, frNode.GetFrom())
}

func s2sFromPublicStore(ctx *state.GenerateState, fromPub *ioswitch2.FromPublicStore, frNode ops2.FromNode) {
	fromStgBld := factory.GetBuilder(&fromPub.UserSpace)
	if !fromStgBld.FeatureDesc().HasBypassPublicRead {
		return
	}

	s2s, err := fromStgBld.CreateS2STransfer()
	if err != nil {
		return
	}

	// 此输出流的所有目的地都要能支持S2S传输
	outVar := frNode.Output().Var()
	if outVar.Dst.Len() == 0 {
		return
	}

	failed := false
	var toPublics []*ops2.PublicWriteNode

loop:
	for i := 0; i < outVar.Dst.Len(); i++ {
		dstNode := outVar.Dst.Get(i)

		switch dstNode := dstNode.(type) {
		case *ops2.PublicWriteNode:
			dstStgBld := factory.GetBuilder(&dstNode.UserSpace)
			if !dstStgBld.FeatureDesc().HasBypassPublicWrite {
				failed = true
				break
			}

			if !s2s.CanTransfer(&dstNode.UserSpace) {
				failed = true
				break
			}

			toPublics = append(toPublics, dstNode)

		default:
			failed = true
			break loop
		}
	}
	if failed {
		return
	}

	for _, toPub := range toPublics {
		s2sNode := ctx.DAG.NewS2STransfer(fromPub.UserSpace, toPub.UserSpace, types.S2SOption{
			DestPathHint: toPub.ObjectPath,
		})
		// 直传指令在目的地Hub上执行
		s2sNode.Env().CopyFrom(toPub.Env())

		// 先获取文件路径，送到S2S节点
		brNode := ctx.DAG.NewBypassFromPublicStore(fromPub.UserSpace, fromPub.Path)
		brNode.Env().CopyFrom(toPub.Env())
		brNode.FilePathVar().ToSlot(s2sNode.SrcPathSlot())

		// 传输结果通知目的节点
		bwNode := ctx.DAG.NewBypassToPublicStore(toPub.UserSpace, toPub.ObjectPath)
		bwNode.Env().CopyFrom(toPub.Env())

		s2sNode.BypassFileInfoVar().ToSlot(bwNode.BypassFileInfoSlot())
		bwNode.BypassCallbackVar().ToSlot(s2sNode.BypassCallbackSlot())

		// 从计划中删除目标节点
		ctx.DAG.RemoveNode(toPub)
		delete(ctx.ToNodes, toPub.To)
	}

	// 从计划中删除源节点
	ctx.DAG.RemoveNode(frNode)
	delete(ctx.FromNodes, frNode.GetFrom())
}
