package commands

import (
	"fmt"

	mh "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash"
	cmds "github.com/jbenet/go-ipfs/commands"
	"github.com/jbenet/go-ipfs/core"
	"github.com/jbenet/go-ipfs/core/commands2/internal"
	dag "github.com/jbenet/go-ipfs/merkledag"
	u "github.com/jbenet/go-ipfs/util"
)

type RefsOutput struct {
	Refs []string
}

var refsCmd = &cmds.Command{
	Description: "Lists link hashes from an object",
	Help: `Retrieves the object named by <ipfs-path> and displays the link
    hashes it contains, with the following format:

    <link base58 hash>`,

	Arguments: []cmds.Argument{
		cmds.Argument{"ipfs-path", cmds.ArgString, true, true,
			"Path to the object(s) to list refs from"},
	},
	Options: []cmds.Option{
		cmds.Option{[]string{"unique", "u"}, cmds.Bool,
			"Omit duplicate refs from output"},
		cmds.Option{[]string{"recursive", "r"}, cmds.Bool,
			"Recursively list links of child nodes"},
	},
	Run: func(res cmds.Response, req cmds.Request) {
		n := req.Context().Node

		opt, found := req.Option("unique")
		unique, ok := opt.(bool)
		if !ok && found {
			unique = false
		}

		opt, found = req.Option("recursive")
		recursive, ok := opt.(bool)
		if !ok && found {
			recursive = false
		}

		paths, err := internal.CastToStrings(req.Arguments())
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		output, err := getRefs(n, paths, unique, recursive)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		res.SetOutput(output)
	},
	Type: &RefsOutput{},
	Marshallers: map[cmds.EncodingType]cmds.Marshaller{
		cmds.Text: func(res cmds.Response) ([]byte, error) {
			output := res.Output().(*RefsOutput)
			s := ""
			for _, ref := range output.Refs {
				s += fmt.Sprintln(ref)
			}
			return []byte(s), nil
		},
	},
}

func getRefs(n *core.IpfsNode, paths []string, unique, recursive bool) (*RefsOutput, error) {
	var refsSeen map[u.Key]bool
	if unique {
		refsSeen = make(map[u.Key]bool)
	}

	refs := make([]string, 0)

	for _, path := range paths {
		object, err := n.Resolver.ResolvePath(path)
		if err != nil {
			return nil, err
		}

		refs, err = addRefs(n, object, refs, refsSeen, recursive)
		if err != nil {
			return nil, err
		}
	}

	return &RefsOutput{refs}, nil
}

func addRefs(n *core.IpfsNode, object *dag.Node, refs []string, refsSeen map[u.Key]bool, recursive bool) ([]string, error) {
	for _, link := range object.Links {
		var found bool
		found, refs = addRef(link.Hash, refs, refsSeen)

		if recursive && !found {
			child, err := n.DAG.Get(u.Key(link.Hash))
			if err != nil {
				return nil, fmt.Errorf("cannot retrieve %s (%s)", link.Hash.B58String(), err)
			}

			refs, err = addRefs(n, child, refs, refsSeen, recursive)
			if err != nil {
				return nil, err
			}
		}
	}

	return refs, nil
}

func addRef(h mh.Multihash, refs []string, refsSeen map[u.Key]bool) (bool, []string) {
	if refsSeen != nil {
		_, found := refsSeen[u.Key(h)]
		if found {
			return true, refs
		}
		refsSeen[u.Key(h)] = true
	}

	refs = append(refs, h.B58String())
	return false, refs
}