package datamodel

import (
	"errors"
	"fmt"
)

// Copy does an explicit shallow copy of a Node's data into a NodeAssembler.
//
// This can be used to flip data from one memory layout to another
// (for example, from basicnode to using using bindnode,
// or to codegenerated node implementations,
// or to or from ADL nodes, etc).
//
// The copy is implemented by ranging over the contents if it's a recursive kind,
// and for each of them, using `AssignNode` on the child values;
// for scalars, it's just calling the appropriate `Assign*` method.
//
// Many NodeAssembler implementations use this as a fallback behavior in their
// `AssignNode` method (that is, they call to this function after all other special
// faster shortcuts they might prefer to employ, such as direct struct copying
// if they share internal memory layouts, etc, have been tried already).
func Copy(n Node, na NodeAssembler) error {
	if n == nil {
		return errors.New("cannot copy a nil node")
	}
	switch n.Kind() {
	case Kind_Null:
		if n.IsAbsent() {
			return errors.New("copying an absent node makes no sense")
		}
		return na.AssignNull()
	case Kind_Bool:
		v, err := n.AsBool()
		if err != nil {
			return fmt.Errorf("node violated contract: promised to be %v kind, but AsBool method returned %w", n.Kind(), err)
		}
		return na.AssignBool(v)
	case Kind_Int:
		v, err := n.AsInt()
		if err != nil {
			return fmt.Errorf("node violated contract: promised to be %v kind, but AsInt method returned %w", n.Kind(), err)
		}
		return na.AssignInt(v)
	case Kind_Float:
		v, err := n.AsFloat()
		if err != nil {
			return fmt.Errorf("node violated contract: promised to be %v kind, but AsFloat method returned %w", n.Kind(), err)
		}
		return na.AssignFloat(v)
	case Kind_String:
		v, err := n.AsString()
		if err != nil {
			return fmt.Errorf("node violated contract: promised to be %v kind, but AsString method returned %w", n.Kind(), err)
		}
		return na.AssignString(v)
	case Kind_Bytes:
		v, err := n.AsBytes()
		if err != nil {
			return fmt.Errorf("node violated contract: promised to be %v kind, but AsBytes method returned %w", n.Kind(), err)
		}
		return na.AssignBytes(v)
	case Kind_Link:
		v, err := n.AsLink()
		if err != nil {
			return fmt.Errorf("node violated contract: promised to be %v kind, but AsLink method returned %w", n.Kind(), err)
		}
		return na.AssignLink(v)
	case Kind_Map:
		ma, err := na.BeginMap(n.Length())
		if err != nil {
			return err
		}
		itr := n.MapIterator()
		for !itr.Done() {
			k, v, err := itr.Next()
			if err != nil {
				return err
			}
			if v.IsAbsent() {
				continue
			}
			if err := ma.AssembleKey().AssignNode(k); err != nil {
				return err
			}
			if err := ma.AssembleValue().AssignNode(v); err != nil {
				return err
			}
		}
		return ma.Finish()
	case Kind_List:
		la, err := na.BeginList(n.Length())
		if err != nil {
			return err
		}
		itr := n.ListIterator()
		for !itr.Done() {
			_, v, err := itr.Next()
			if err != nil {
				return err
			}
			if v.IsAbsent() {
				continue
			}
			if err := la.AssembleValue().AssignNode(v); err != nil {
				return err
			}
		}
		return la.Finish()
	default:
		return fmt.Errorf("node has invalid kind %v", n.Kind())
	}
}
