// cmhshtool -- dealing with KEKS-encoded cm/hashed utility
// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program.  If not, see <http://www.gnu.org/licenses/>.

package main

import (
	"crypto/sha512"
	"flag"
	"fmt"
	"log"
	"os"

	"go.cypherpunks.su/gogost/v6/gost34112012256"
	"go.cypherpunks.su/gogost/v6/gost34112012512"

	"go.cypherpunks.su/keks"
	cmhash "go.cypherpunks.su/keks/cm/hash"
	cmblake2b "go.cypherpunks.su/keks/cm/hash/blake2b"
	"go.cypherpunks.su/keks/cm/hash/gost"
	"go.cypherpunks.su/keks/cm/hash/merkle"
)

func main() {
	workers := flag.Int("p", cmhash.DefaultNumCPU, "Parallel workers")
	chunkLenK := flag.Int("c", merkle.DefaultChunkLen/1024, "Chunk size, KiB")
	algo := flag.String("a", cmhash.BLAKE2bMerkle, "Algorithm to use")
	list := flag.Bool("list", false, "List available algorithms")
	mmap := flag.String("mmap", "", "Use that mmap-ed file instead of stdin")
	flag.Parse()
	if *list {
		fmt.Println(cmhash.BLAKE2bMerkle)
		fmt.Println(cmhash.SHA2512 + "-merkle")
		fmt.Println(cmhash.SHAKE128Merkle)
		fmt.Println(cmhash.SHAKE256Merkle)
		fmt.Println(cmhash.Streebog256Merkle)
		fmt.Println(cmhash.Streebog512Merkle)
		return
	}
	chunkLen := 1024 * *chunkLenK
	var hasher *merkle.Hasher
	switch *algo {
	case cmhash.BLAKE2bMerkle:
		hasher = cmblake2b.NewMerkleHasher(chunkLen, *workers).(*merkle.Hasher)
	case cmhash.SHA2512 + "-merkle":
		hasher = merkle.NewHasherPrefixed(sha512.New, chunkLen, *workers)
	case cmhash.SHAKE128Merkle:
		hasher = cmhash.NewSHAKE128MerkleHasher(chunkLen, *workers).(*merkle.Hasher)
	case cmhash.SHAKE256Merkle:
		hasher = cmhash.NewSHAKE256MerkleHasher(chunkLen, *workers).(*merkle.Hasher)
	case cmhash.Streebog256Merkle:
		hasher = gost.NewMerkleHasher(gost34112012256.New, chunkLen, *workers).(*merkle.Hasher)
	case cmhash.Streebog512Merkle:
		hasher = gost.NewMerkleHasher(gost34112012512.New, chunkLen, *workers).(*merkle.Hasher)
	default:
		log.Fatal("unknown -a")
	}
	var err error
	if *mmap == "" {
		_, err = hasher.DoReadFrom(os.Stdin)
	} else {
		err = hasher.Mmap(*mmap)
	}
	if err != nil {
		log.Fatal(err)
	}
	_, err = keks.Encode(os.Stdout, cmhash.Magic, nil)
	if err != nil {
		log.Fatal(err)
	}
	_, err = keks.Encode(os.Stdout, map[string][]byte{*algo: hasher.Sum(nil)}, nil)
	if err != nil {
		log.Fatal(err)
	}
}
