#!/usr/bin/env ruby
$:.unshift( File.expand_path("../../lib", __FILE__) )
require 'bitcoin'

Bitcoin.network = :testnet3
#DATA_DIR = File.expand_path("../data", __FILE__)
CHAIN    = { main: {}, side: {}, orphan: {} }
WALLET   = {}

def current_height(branch=:main); CHAIN[branch].size-1; end
def next_height(branch=:main); current_height(branch) + 1; end
def gen_bip34_height(height); buf = [height].pack("V").gsub(/\x00+$/,""); [buf.bytesize, buf].pack("Ca*"); end

def build_coinbase_tx(next_height, fees=0)
  tx = Bitcoin::P::Tx.new
  input = Bitcoin::P::TxIn.new(Bitcoin::P::TxIn::NULL_HASH, Bitcoin::P::TxIn::COINBASE_INDEX, 0, "", Bitcoin::P::TxIn::DEFAULT_SEQUENCE)
  input.script = gen_bip34_height(next_height) + "generated by bitcoin-ruby for testbox"
  tx.inputs << input

  key = Bitcoin::Key.generate
  WALLET[key.addr] = { privkey: key.to_base58 }
  output = Bitcoin::P::TxOut.new(Bitcoin.block_creation_reward(next_height) + fees, Bitcoin::Script.from_string("#{key.pub} OP_CHECKSIG").to_payload)
  tx.outputs << output

  Bitcoin::P::Tx.new(tx.to_payload)
end

def build_next_block(prev_block, next_height, txs=[], time=Time.now.to_i, fees=0)
  b = Bitcoin::P::Block.new(nil)
  b.prev_block = prev_block ? prev_block.hash.htb.reverse : "\x00"*32
  b.bits = prev_block ? prev_block.bits : Bitcoin.network[:proof_of_work_limit]
  b.ver, b.nonce, b.time = 2, 0, time
  b.tx = [ build_coinbase_tx(next_height, fees), *txs ]
  b.mrkl_root = Bitcoin.hash_mrkl_tree(b.tx.map(&:hash)).last.htb.reverse

  target = Bitcoin.decode_compact_bits(b.bits).to_i(16)
  b.recalc_block_hash
  until b.hash.to_i(16) < target
    b.nonce += 1
    (b.time += 1; b.nonce = 0) if b.nonce > 0xffffffff
    b.recalc_block_hash
  end

  raise "Payload Error"  unless Bitcoin::P::Block.new(b.to_payload).to_payload == b.to_payload
  b
end

#b = Bitcoin::P::Block.from_json_file(File.join(DATA_DIR, Bitcoin.network[:genesis_hash] + ".json"))
#CHAIN[:main][next_height(:main)] = b.to_json

Bitcoin.network[:checkpoints] = {}
Bitcoin.network[:proof_of_work_limit] = Bitcoin.encode_compact_bits("00ffff0000000000000000000000000000000000000000000000000000000000")
Bitcoin.network[:coinbase_maturity] = 5
Bitcoin.network[:retarget_interval] = 126   # 2016
Bitcoin.network[:retarget_time] = 3600      # 1209600
Bitcoin.network[:next_block_time_target] = Bitcoin.network[:retarget_time] / Bitcoin.network[:retarget_interval] # 28 seconds

time = Time.now.to_i - (3600*2) # 2 hours ago

b = build_next_block(nil, next_height(:main), [], time)
CHAIN[:main][next_height(:main)] = b
Bitcoin.network[:genesis_hash] = b.hash

6.times{
  b = build_next_block(b, next_height(:main), [], time+=Bitcoin.network[:next_block_time_target])
  CHAIN[:main][next_height(:main)] = b
}

# for the 8th block redeem the output from 2nd block and send to two new addresses
prev_tx, prev_tx_output_index = CHAIN[:main][1].tx.first, 0
value = prev_tx.outputs[prev_tx_output_index].value
new_tx = Bitcoin::Protocol::Tx.new
new_tx.add_in Bitcoin::Protocol::TxIn.new(prev_tx.binary_hash, prev_tx_output_index, 0)

min_fee = 50_000
next_fees = 0
key = Bitcoin::Key.generate
WALLET[key.addr] = { privkey: key.to_base58 }
new_tx.add_out Bitcoin::Protocol::TxOut.value_to_address((value/2)-min_fee, key.addr)
next_fees += min_fee

key = Bitcoin::Key.generate
WALLET[key.addr] = { privkey: key.to_base58 }
new_tx.add_out Bitcoin::Protocol::TxOut.value_to_address((value/2)-min_fee, key.addr)
next_fees += min_fee

input_index = 0
privkey = WALLET[ Bitcoin::Script.new(prev_tx.outputs[prev_tx_output_index].script).get_address ][:privkey]
key = Bitcoin::Key.from_base58(privkey)
signature = key.sign(new_tx.signature_hash_for_input(input_index, prev_tx))
#tx.in[input_index].script_sig = Bitcoin::Script.to_signature_pubkey_script(signature, [key.public_key_hex].pack("H*"))
new_tx.in[input_index].script_sig = Bitcoin::Script.to_signature_pubkey_script(signature, nil)

new_tx = Bitcoin::Protocol::Tx.new( new_tx.to_payload )
raise "failed to generate testbox tx" unless new_tx.verify_input_signature(input_index, prev_tx) == true


b = build_next_block(b, next_height(:main), [new_tx], time+=Bitcoin.network[:next_block_time_target], next_fees)
main_b = b
CHAIN[:main][last_height=next_height(:main)] = b

height = next_height(:main)
CHAIN[:main][height] = []

# "transaction list must be non-empty"
b = build_next_block(b, height, [], time+=Bitcoin.network[:next_block_time_target])
b.tx = []
CHAIN[:main][height] << b

# "Block merkle root mismatch!"
#b = build_next_block(b, height, [], time+=Bitcoin.network[:next_block_time_target])
#b.instance_eval{ @merkle_root = b.hash }
#CHAIN[:main][height] << b

# proof of work wrong
b = build_next_block(b, height, [], time+=Bitcoin.network[:next_block_time_target])
b.tx << build_next_block(b, height, [], time+=Bitcoin.network[:next_block_time_target]).tx.first
b.recalc_mrkl_root
b.recalc_block_hash
CHAIN[:main][height] << b

# two coinbase txs
b = build_next_block(b, height, [], time+=Bitcoin.network[:next_block_time_target])
b = build_next_block(b, height, [b.tx.first], time+=Bitcoin.network[:next_block_time_target])
CHAIN[:main][height] << b

# valid block
b = build_next_block(b, height, [], time+=Bitcoin.network[:next_block_time_target])
CHAIN[:main][height] << b


# empty tx outputs
prev_tx, prev_tx_output_index = CHAIN[:main][1].tx.first, 0
value = prev_tx.outputs[prev_tx_output_index].value
new_tx = Bitcoin::Protocol::Tx.new
new_tx.add_in Bitcoin::Protocol::TxIn.new(prev_tx.binary_hash, prev_tx_output_index, 0)
new_tx.in[input_index].script_sig = "fail"
new_tx = Bitcoin::Protocol::Tx.new( new_tx.to_payload )
b = build_next_block(b, height, [new_tx], time+=Bitcoin.network[:next_block_time_target])
CHAIN[:main][height] << b


# empty tx inputs
prev_tx, prev_tx_output_index = CHAIN[:main][1].tx.first, 0
value = prev_tx.outputs[prev_tx_output_index].value
new_tx = Bitcoin::Protocol::Tx.new
key = Bitcoin::Key.generate
WALLET[key.addr] = { privkey: key.to_base58 }
new_tx.add_out Bitcoin::Protocol::TxOut.value_to_address(value, key.addr)
new_tx = Bitcoin::Protocol::Tx.new( new_tx.to_payload )
b = build_next_block(b, height, [new_tx], time+=Bitcoin.network[:next_block_time_target])
CHAIN[:main][height] << b



puts JSON.pretty_generate({ chain: CHAIN, wallet: WALLET })
