#!/usr/bin/env bash

start_height=$1
count=$2
target=$3
deviation=$4
power=$5

if [ -z "${start_height}" ]; then
  echo
  echo "Error: missing start height."
  echo
  echo "Usage:"
  echo "block-timings.sh <startheight> [count] [target] [deviation] [power]"
  echo
  echo "startheight: a block height, preferably within the untrimmed range, to avoid data gaps"
  echo "count: the number of blocks to request and analyse after the start height. Default: 100"
  echo "target: the target block time in milliseconds. Originates from blockchain.json. Default: 60000"
  echo "deviation: the allowed block time deviation in milliseconds. Originates from blockchain.json. Default: 30000"
  echo "power: used when transforming key distance to a time offset. Originates from blockchain.json. Default: 0.2"
  echo
  exit
fi

count=${count:=100}
target=${target:=60000}
deviation=${deviation:=30000}
power=${power:=0.2}

finish_height=$((start_height + count - 1))
height=$start_height

echo "Settings:"
echo "Target time offset: ${target}"
echo "Deviation: ${deviation}"
echo "Power transform: ${power}"
echo

function calculate_time_offset {
  local key_distance_ratio=$1
  local transformed=$( echo "" | awk "END {print ${key_distance_ratio} ^ ${power}}")
  local time_offset=$(echo "${deviation}*2*${transformed}" | bc)
  time_offset=${time_offset%.*}
  echo $time_offset
}


function fetch_and_process_blocks {

  echo "Fetching blocks from height ${start_height} to ${finish_height}..."
  echo

  total_time_offset=0
  errors=0

  while [ "${height}" -le "${finish_height}" ]; do
    block_minting_info=$(curl -s "http://localhost:12391/blocks/byheight/${height}/mintinginfo")
    error=$(echo "${block_minting_info}" | jq -r .error)
    if [ "${error}" != "null" ]; then
      echo "Error fetching minting info for block ${height}"
      echo
      errors=$((errors+1))
      height=$((height+1))
      continue;
    fi

    # Parse minting info
    minter_level=$(echo "${block_minting_info}" | jq -r .minterLevel)
    online_accounts_count=$(echo "${block_minting_info}" | jq -r .onlineAccountsCount)
    key_distance_ratio=$(echo "${block_minting_info}" | jq -r .keyDistanceRatio)
    time_delta=$(echo "${block_minting_info}" | jq -r .timeDelta)
    timestamp=$(echo "${block_minting_info}" | jq -r .timestamp)

    time_offset=$(calculate_time_offset "${key_distance_ratio}")
    block_time=$((target-deviation+time_offset))

    echo "=== BLOCK ${height} ==="
    echo "Timestamp: ${timestamp}"
    echo "Minter level: ${minter_level}"
    echo "Online accounts: ${online_accounts_count}"
    echo "Key distance ratio: ${key_distance_ratio}"
    echo "Time offset: ${time_offset}"
    echo "Block time (real): ${time_delta}"
    echo "Block time (calculated): ${block_time}"

    if [ "${time_delta}" -ne "${block_time}" ]; then
      echo "WARNING: Block time mismatch. This is to be expected when using custom settings."
    fi
    echo

    total_time_offset=$((total_time_offset+block_time))

    height=$((height+1))
  done

  adjusted_count=$((count-errors))
  if [ "${adjusted_count}" -eq 0 ]; then
    echo "No blocks were retrieved."
    echo
    exit;
  fi

  mean_time_offset=$((total_time_offset/adjusted_count))
  time_offset_diff=$((mean_time_offset-target))

  echo "==================="
  echo "===== SUMMARY ====="
  echo "==================="
  echo "Total blocks retrieved: ${adjusted_count}"
  echo "Total blocks failed: ${errors}"
  echo "Mean time offset: ${mean_time_offset}ms"
  echo "Target time offset: ${target}ms"
  echo "Difference from target: ${time_offset_diff}ms"
  echo

}

function estimate_key_distance_ratio_for_level {
  local level=$1
  local example_key_distance="0.5"
  echo "(${example_key_distance}/${level})"
}

function estimate_block_timestamps {
  min_block_time=9999999
  max_block_time=0

  echo "===== BLOCK TIME ESTIMATES ====="

  for level in {1..10}; do
    example_key_distance_ratio=$(estimate_key_distance_ratio_for_level "${level}")
    time_offset=$(calculate_time_offset "${example_key_distance_ratio}")
    block_time=$((target-deviation+time_offset))

    if [ "${block_time}" -gt "${max_block_time}" ]; then
      max_block_time=${block_time}
    fi
    if [ "${block_time}" -lt "${min_block_time}" ]; then
      min_block_time=${block_time}
    fi

    echo "Level: ${level}, time offset: ${time_offset}, block time: ${block_time}"
  done
  block_time_range=$((max_block_time-min_block_time))
  echo "Range: ${block_time_range}"
  echo
}

fetch_and_process_blocks
estimate_block_timestamps