/*
 *
 * Copyright 2015 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
// Package main implements a gRPC test client for lightwalletd.
// This file adapted from:
//   https://github.com/grpc/grpc-go/blob/master/examples/helloworld/greeter_client/main.go
// For now at least, all it does is generate a load for performance and stress testing.
package main

import (
	"context"
	"flag"
	"io"
	"log"
	"strconv"
	"sync"
	"time"

	pb "github.com/adityapk00/lightwalletd/walletrpc"
	"google.golang.org/grpc"
)

const (
	address = "localhost:9067"
)

type Options struct {
	concurrency int    `json:"concurrency"`
	iterations  int    `json:"iterations"`
	op          string `json:"op"`
	verbose     *bool  `json:"v"`
}

func main() {
	opts := &Options{}
	flag.IntVar(&opts.concurrency, "concurrency", 1, "number of threads")
	flag.IntVar(&opts.iterations, "iterations", 1, "number of iterations")
	flag.StringVar(&opts.op, "op", "ping", "operation(ping|getlightdinfo|getblock|getblockrange)")
	opts.verbose = flag.Bool("v", false, "verbose (print operation results)")
	flag.Parse()

	// Remaining args are all integers (at least for now)
	args := make([]int64, flag.NArg())
	for i := 0; i < flag.NArg(); i++ {
		var err error
		if args[i], err = strconv.ParseInt(flag.Arg(i), 10, 64); err != nil {
			log.Fatalf("argument %v is not an int64: %v", flag.Arg(i), err)
		}
	}

	// Set up a connection to the server.
	conn, err := grpc.Dial(address, grpc.WithInsecure(),
		grpc.WithConnectParams(grpc.ConnectParams{MinConnectTimeout: 30 * time.Second}))
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := pb.NewCompactTxStreamerClient(conn)

	ctx, cancel := context.WithTimeout(context.Background(), 100000*time.Second)
	defer cancel()
	var wg sync.WaitGroup
	wg.Add(opts.concurrency)
	for i := 0; i < opts.concurrency; i++ {
		go func(i int) {
			for j := 0; j < opts.iterations; j++ {
				switch opts.op {
				case "ping":
					var a pb.Duration
					a.IntervalUs = 8 * 1000 * 1000 // default 8 seconds
					if len(args) > 0 {
						a.IntervalUs = args[0]
					}
					r, err := c.Ping(ctx, &a)
					if err != nil {
						log.Fatalf("Ping failed: %v", err)
					}
					if *opts.verbose {
						log.Println("thr:", i, "entry:", r.Entry, "exit:", r.Exit)
					}
				case "getlightdinfo":
					r, err := c.GetLightdInfo(ctx, &pb.Empty{})
					if err != nil {
						log.Fatalf("GetLightwalletdInfo failed: %v", err)
					}
					if *opts.verbose {
						log.Println("thr:", i, r)
					}
				case "getblock":
					blockid := &pb.BlockID{Height: 748400} // default (arbitrary)
					if len(args) > 0 {
						blockid.Height = uint64(args[0])
					}
					r, err := c.GetBlock(ctx, blockid)
					if err != nil {
						log.Fatalf("GetLightwalletdInfo failed: %v", err)
					}
					// Height is enough to see if it's working
					if *opts.verbose {
						log.Println("thr:", i, r.Height)
					}
				case "getblockrange":
					blockrange := &pb.BlockRange{ // defaults (arbitrary)
						Start: &pb.BlockID{Height: 738100},
						End:   &pb.BlockID{Height: 738199},
					}
					if len(args) > 0 {
						blockrange.Start.Height = uint64(args[0])
						blockrange.End.Height = uint64(args[1])
					}
					stream, err := c.GetBlockRange(ctx, blockrange)
					if err != nil {
						log.Fatalf("GetLightwalletdInfo failed: %v", err)
					}
					for {
						// each call to Recv returns a compact block
						r, err := stream.Recv()
						if err == io.EOF {
							break
						}
						if err != nil {
							log.Fatal(err)
						}
						// Height is enough to see if it's working
						if *opts.verbose {
							log.Println("thr:", i, r.Height)
						}
					}
				default:
					log.Fatalf("unknown op %s", opts.op)
				}
			}
			wg.Done()
		}(i)
	}
	wg.Wait()
}
