// Copyright (C) 2017  Nexedi SA and Contributors.
//                     Kirill Smelkov <kirr@nexedi.com>
//
// This program is free software: you can Use, Study, Modify and Redistribute
// it under the terms of the GNU General Public License version 3, or (at your
// option) any later version, as published by the Free Software Foundation.
//
// You can also Link and Combine this program with other software covered by
// the terms of any of the Free Software licenses or any of the Open Source
// Initiative approved licenses and Convey the resulting work. Corresponding
// source of such a combination shall include the source code for all other
// software used.
//
// This program is distributed WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// See COPYING file for full licensing terms.
// See https://www.nexedi.com/licensing for rationale and options.

package zodbtools
// Catobj - dump content of a database object

import (
	"context"
	"flag"
	"fmt"
	"io"
	"os"

	"lab.nexedi.com/kirr/go123/prog"
	"lab.nexedi.com/kirr/neo/go/zodb"
)


// Catobj dumps content of one ZODB object.
//
// The object is printed in raw form without any headers (see Dumpobj)
func Catobj(ctx context.Context, w io.Writer, stor zodb.IStorage, xid zodb.Xid) error {
	buf, _, err := stor.Load(ctx, xid)
	if err != nil {
		return err
	}

	_, err = w.Write(buf.Data) // NOTE deleted data are returned as err by Load
	buf.Release()
	if err != nil {
		err = fmt.Errorf("%s: catobj %s: %s", stor.URL(), xid, err)
	}
	return err
}

// Dumpobj dumps content of one ZODB object with zodbdump-like header
func Dumpobj(ctx context.Context, w io.Writer, stor zodb.IStorage, xid zodb.Xid, hashOnly bool) error {
	var objInfo zodb.DataInfo

	buf, serial, err := stor.Load(ctx, xid)
	if err != nil {
		return err
	}

	objInfo.Oid = xid.Oid
	objInfo.Tid = serial
	objInfo.Data = buf.Data
	objInfo.DataTidHint = 0 // no copy detection at catobj - just dump raw content

	d := dumper{W: w, HashOnly: hashOnly}
	err = d.DumpData(&objInfo)
	buf.Release()
	return err
}

// ----------------------------------------
const catobjSummary = "dump content of a database object"

func catobjUsage(w io.Writer) {
	fmt.Fprintf(w,
`Usage: zodb catobj [OPTIONS] <storage> xid...
Dump content of a ZODB database object.

<storage> is an URL (see 'zodb help zurl') of a ZODB-storage.
xid is object address (see 'zodb help xid').

Options:

	-h --help       this help text.
	-hashonly	dump only hashes of objects without content.
	-raw		dump object data without any headers. Only one object allowed.
`)
}

func catobjMain(argv []string) {
	hashOnly := false
	raw := false

	flags := flag.FlagSet{Usage: func() { catobjUsage(os.Stderr) }}
	flags.Init("", flag.ExitOnError)
	flags.BoolVar(&hashOnly, "hashonly", hashOnly, "dump only hashes of objects")
	flags.BoolVar(&raw, "raw", hashOnly, "dump object data without any headers. Only one object allowed.")
	// TODO also -batch to serve objects a-la `git cat-file --batch` ?
	flags.Parse(argv[1:])

	argv = flags.Args()
	if len(argv) < 2 {
		flags.Usage()
		prog.Exit(2)
	}
	storUrl := argv[0]

	if hashOnly && raw {
		prog.Fatal("-hashonly & -raw are incompatible")
	}

	xidv := []zodb.Xid{}
	for _, arg := range argv[1:] {
		xid, err := zodb.ParseXid(arg)
		if err != nil {
			prog.Fatal(err)
		}

		xidv = append(xidv, xid)
	}

	if raw && len(xidv) > 1 {
		prog.Fatal("only 1 object allowed with -raw")
	}

	ctx := context.Background()

	stor, err := zodb.OpenStorage(ctx, storUrl, &zodb.OpenOptions{ReadOnly: true})
	if err != nil {
		prog.Fatal(err)
	}
	// TODO defer stor.Close()

	catobj := func(xid zodb.Xid) error {
		if raw {
			return Catobj(ctx, os.Stdout, stor, xid)
		} else {
			return Dumpobj(ctx, os.Stdout, stor, xid, hashOnly)
		}
	}

	for _, xid := range xidv {
		err = catobj(xid)
		if err != nil {
			prog.Fatal(err)
		}
	}
}
