<?php

define("ADDRESSVERSION","00"); //this is a hex byte

function decodeHex($hex)
{
	$hex=strtoupper($hex);
	$chars="0123456789ABCDEF";
	$return="0";
	for($i=0;$i<strlen($hex);$i++)
	{
		$current=(string)strpos($chars,$hex[$i]);
		$return=(string)bcmul($return,"16",0);
		$return=(string)bcadd($return,$current,0);
	}
	return $return;
}

function encodeHex($dec)
{
	$chars="0123456789ABCDEF";
	$return="";
	while (bccomp($dec,0)==1)
	{
		$dv=(string)bcdiv($dec,"16",0);
		$rem=(integer)bcmod($dec,"16");
		$dec=$dv;
		$return=$return.$chars[$rem];
	}
	return strrev($return);
}

function decodeBase58($base58)
{
	$origbase58=$base58;

	$chars="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
	$return="0";
	for($i=0;$i<strlen($base58);$i++)
	{
		$current=(string)strpos($chars,$base58[$i]);
		$return=(string)bcmul($return,"58",0);
		$return=(string)bcadd($return,$current,0);
	}

	$return=encodeHex($return);

	//leading zeros
	for($i=0;$i<strlen($origbase58)&&$origbase58[$i]=="1";$i++)
	{
		$return="00".$return;
	}

	if(strlen($return)%2!=0)
	{
		$return="0".$return;
	}

	return $return;
}

function encodeBase58($hex)
{
	if(strlen($hex)%2!=0)
	{
		die("encodeBase58: uneven number of hex characters");
	}
	$orighex=$hex;

	$chars="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
	$hex=decodeHex($hex);
	$return="";
	while (bccomp($hex,0)==1)
	{
		$dv=(string)bcdiv($hex,"58",0);
		$rem=(integer)bcmod($hex,"58");
		$hex=$dv;
		$return=$return.$chars[$rem];
	}
	$return=strrev($return);

	//leading zeros
	for($i=0;$i<strlen($orighex)&&substr($orighex,$i,2)=="00";$i+=2)
	{
		$return="1".$return;
	}

	return $return;
}

function hash160ToAddress($hash160,$addressversion=ADDRESSVERSION)
{
	$hash160=$addressversion.$hash160;
	$check=pack("H*" , $hash160);
	$check=hash("sha256",hash("sha256",$check,true));
	$check=substr($check,0,8);
	$hash160=strtoupper($hash160.$check);
	return encodeBase58($hash160);
}

function addressToHash160($addr)
{
	$addr=decodeBase58($addr);
	$addr=substr($addr,2,strlen($addr)-10);
	return $addr;
}

function checkAddress($addr,$addressversion=ADDRESSVERSION)
{
	$addr=decodeBase58($addr);
	if(strlen($addr)!=50)
	{
		return false;
	}
	$version=substr($addr,0,2);
	if(hexdec($version)>hexdec($addressversion))
	{
		return false;
	}
	$check=substr($addr,0,strlen($addr)-8);
	$check=pack("H*" , $check);
	$check=strtoupper(hash("sha256",hash("sha256",$check,true)));
	$check=substr($check,0,8);
	return $check==substr($addr,strlen($addr)-8);
}

function hash160($data)
{
	$data=pack("H*" , $data);
	return strtoupper(hash("ripemd160",hash("sha256",$data,true)));
}

function pubKeyToAddress($pubkey)
{
	return hash160ToAddress(hash160($pubkey));
}

function remove0x($string)
{
	if(substr($string,0,2)=="0x"||substr($string,0,2)=="0X")
	{
		$string=substr($string,2);
	}
	return $string;
}

// version is used for multi algo coins
function versionToAlgo($coin, $version)
{
	// could be filled by block json (chain analysis)
	$algos['MYR'] = array(
		0=>'sha256', 1=>'scrypt', 2=>'myr-gr', 3=>'skein', 4=>'qubit', 5=>'yescrypt'
	);
	$algos['DGB'] = array(
		0=>'scrypt', 1=>'sha256', 2=>'myr-gr', 3=>'skein', 4=>'qubit'
	);
	$algos['AUR'] = array(
		0=>'sha256', 1=>'scrypt', 2=>'myr-gr', 3=>'skein', 4=>'qubit'
	);
	$algos['DGC'] = array(
		0=>'scrypt', 1=>'sha256', 2=>'x11'
	);
	$algos['DUO'] = array(
		0=>'sha256', 1=>'scrypt'
	);
	$algos['J'] = array(
		2 =>'sha256', 3=>'x11', 4=>'x13', 5=>'x15', 6=>'scrypt',
		7 =>'nist5',  8 =>'myr-gr', 9=>'penta', 10=>'whirlpool',
		11=>'luffa',  12=>'keccak', 13=>'quark', 15=>'bastion'
	);
	$algos['GCH'] = array(
		0=>'x12', 1=>'x11', 2=>'x13', 3=>'sha256', 4=>'blake2s'
	);
	$algos['GLT'] = array(
		0=>'sha256', 1=>'scrypt', 2=>'x11', 3=>'neoscrypt', 4=>'equihash', 5=>'yescrypt', 6=>'hmq1725', 
		7=>'xevan', 8=>'nist5', 9=>'bitcore', 10=>'pawelhash', 11=>'x13', 12=>'x14', 13=>'x15', 14=>'x17', 
		15=>'lyra2v2', 16=>'blake2s', 17=>'blake2b', 18=>'astralhash', 19=>'padihash', 20=>'jeonghash', 
		21=>'keccak', 22=>'zhash', 23=>'globalhash', 24=>'skein', 25=>'myr-gr', 26=>'qubit', 27=>'skunk', 
		28=>'quark', 29=>'x16r'
	);
	$algos['RICHX'] = array(
		0=>'sha256', 1=>'scrypt', 2=>'myr-gr', 3=>'skein', 4=>'qubit'
	);
	$algos['SFR'] = array(
		0=>'sha256', 1=>'scrypt', 2=>'myr-gr', 3=>'x11', 4=>'blake'
	);
	$algos['UIS'] = array(
		0=>'lyra2v2', 1=>'skein', 2=>'qubit', 3=>'yescrypt', 4=>'x11'
	);
	$algos['XVG'] = array(
		0=>'scrypt', 1=>'scrypt', 2=>'myr-gr', 3=>'x17', 4=>'blake2s', 10=>'lyra2v2',
	);
	$algos['XSH'] = array(
		0=>'scrypt', 1=>'scrypt', 2=>'myr-gr', 3=>'x17', 4=>'blake2s', 10=>'lyra2v2', 11=>'x16s',
	);
	$algos['ARG'] = array(
		0=>'sha256', 1=>'scrypt', 2=>'lyra2v2', 3=>'myr-gr', 4=>'argon2d', 5=>'yescrypt',
	);
	$symbol = $coin->symbol;
	if (!empty($coin->symbol2)) $symbol = $coin->symbol2;

	if ($symbol == 'J')
		return arraySafeVal($algos[$symbol], $version, '');
	else if($symbol == 'GCH')
		return arraySafeVal($algos[$symbol], ($version - 9), '');
	else if($symbol == 'XVG')
		return arraySafeVal($algos[$symbol], ($version >> 11), 'scrypt');
	else if($symbol == 'XSH')
		return arraySafeVal($algos[$symbol], (($version-536870000) >> 11), 'scrypt');
	else if (isset($algos[$symbol]))
		return arraySafeVal($algos[$symbol], ($version >> 9) & 7, '');
	return false;
}

function vexon_explorer_h($value)
{
	return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
}

function vexon_explorer_short($value, $left=12, $right=10)
{
	$value = (string)$value;
	if (strlen($value) <= $left + $right + 3)
		return $value;
	return substr($value, 0, $left).'...'.substr($value, -$right);
}

function vexon_explorer_time($timestamp)
{
	$timestamp = intval($timestamp);
	if ($timestamp <= 0)
		return 'Waiting';
	return date('Y-m-d H:i:s T', $timestamp);
}

function vexon_explorer_amount($amount, $places=8)
{
	if ($amount === null || $amount === '')
		return '0';
	$value = number_format((double)$amount, $places, '.', '');
	$value = rtrim(rtrim($value, '0'), '.');
	return $value === '' ? '0' : $value;
}

function vexon_explorer_metric($value, $places=3)
{
	if ($value === null || $value === '' || !is_numeric($value))
		return 'Waiting';
	$value = (double)$value;
	$units = array('', 'K', 'M', 'B', 'T', 'P');
	$index = 0;
	while (abs($value) >= 1000 && $index < count($units)-1) {
		$value /= 1000;
		$index++;
	}
	$text = number_format($value, $places, '.', '');
	$text = rtrim(rtrim($text, '0'), '.');
	return trim($text.' '.$units[$index]);
}

function vexon_explorer_hashrate($difficulty, $block_time=150)
{
	if ($difficulty === null || $difficulty === '' || !is_numeric($difficulty))
		return 'Waiting';
	$block_time = max(1, intval($block_time));
	$hashrate = (double)$difficulty * 4294967296.0 / $block_time;
	return trim(vexon_explorer_metric($hashrate, 2).'h/s');
}

function vexon_explorer_rpc($remote, $method, $params=array())
{
	if (!$remote || !is_object($remote))
		return false;
	try {
		return call_user_func_array(array($remote, $method), $params);
	} catch (Exception $e) {
		return false;
	}
}

function vexon_explorer_query_row($sql, $params=array())
{
	try {
		return dborow($sql, $params);
	} catch (Exception $e) {
		return array();
	}
}

function vexon_explorer_query_list($sql, $params=array())
{
	try {
		return dbolist($sql, $params);
	} catch (Exception $e) {
		return array();
	}
}

function vexon_explorer_query_scalar($sql, $params=array(), $default=0)
{
	try {
		$value = dboscalar($sql, $params);
		return $value === null ? $default : $value;
	} catch (Exception $e) {
		return $default;
	}
}

function vexon_explorer_is_hash($value)
{
	return is_string($value) && strlen($value) >= 32 && ctype_xdigit($value);
}

function vexon_explorer_is_address($value)
{
	return is_string($value) && preg_match('/^V[1-9A-HJ-NP-Za-km-z]{24,45}$/', $value);
}

function vexon_explorer_validate_address($remote, $address)
{
	$address = trim((string)$address);
	$result = vexon_explorer_rpc($remote, 'validateaddress', array($address));
	if (is_array($result)) {
		$result['address'] = arraySafeVal($result, 'address', $address);
		$result['isvalid'] = !!arraySafeVal($result, 'isvalid', false);
		return $result;
	}

	return array(
		'address'=>$address,
		'isvalid'=>vexon_explorer_is_address($address),
		'warning'=>'Address validation RPC is unavailable.',
	);
}

function vexon_explorer_tx_outputs($tx)
{
	$outputs = array();
	$total = 0.0;
	$vouts = arraySafeVal($tx, 'vout', array());
	if (!is_array($vouts))
		return array('outputs'=>$outputs, 'total'=>0);

	foreach($vouts as $vout) {
		$value = (double) arraySafeVal($vout, 'value', 0);
		$total += $value;
		$script = arraySafeVal($vout, 'scriptPubKey', array());
		$addresses = arraySafeVal($script, 'addresses', array());
		if (!is_array($addresses))
			$addresses = array();
		$outputs[] = array(
			'value'=>$value,
			'addresses'=>$addresses,
			'type'=>arraySafeVal($script, 'type', ''),
		);
	}

	return array('outputs'=>$outputs, 'total'=>$total);
}

function vexon_explorer_tx_value_for_address($tx, $address)
{
	$address = (string)$address;
	$total = 0.0;
	$outputs = vexon_explorer_tx_outputs($tx);
	foreach($outputs['outputs'] as $out) {
		$addresses = arraySafeVal($out, 'addresses', array());
		if (is_array($addresses) && in_array($address, $addresses))
			$total += (double) arraySafeVal($out, 'value', 0);
	}
	return $total;
}

function vexon_explorer_chain_transactions($remote, $address, $limit=20)
{
	$limit = max(1, min(100, intval($limit)));
	$txs = vexon_explorer_rpc($remote, 'searchrawtransactions', array($address, 1, 0, $limit));
	if (!is_array($txs))
		return array('available'=>false, 'transactions'=>array());

	$rows = array();
	foreach($txs as $tx) {
		if (!is_array($tx))
			continue;
		$rows[] = array(
			'txid'=>arraySafeVal($tx, 'txid', ''),
			'confirmations'=>intval(arraySafeVal($tx, 'confirmations', 0)),
			'time'=>arraySafeVal($tx, 'time', arraySafeVal($tx, 'blocktime', 0)),
			'blockhash'=>arraySafeVal($tx, 'blockhash', ''),
			'received'=>vexon_explorer_tx_value_for_address($tx, $address),
		);
	}

	return array('available'=>true, 'transactions'=>$rows);
}
