// Copyright 2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.

// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Parity 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 General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.

use client::MiningBlockChainClient;
use transaction::SignedTransaction;
use util::{U256, Uint, Mutex};

const SERVICE_TRANSACTION_CONTRACT_REGISTRY_NAME: &'static str = "service_transaction_checker";

/// Service transactions checker.
#[derive(Default)]
pub struct ServiceTransactionChecker {
	contract: Mutex<Option<provider::Contract>>,
}

impl ServiceTransactionChecker {
	/// Try to create instance, reading contract address from given chain client.
	pub fn update_from_chain_client(&self, client: &MiningBlockChainClient) {
		let mut contract = self.contract.lock();
		if contract.is_none() {
			*contract = client.registry_address(SERVICE_TRANSACTION_CONTRACT_REGISTRY_NAME.to_owned())
				.and_then(|contract_addr| {
					trace!(target: "txqueue", "Configuring for service transaction checker contract from {}", contract_addr);

					Some(provider::Contract::new(contract_addr))
				})
		}
	}

	/// Checks if service transaction can be appended to the transaction queue.
	pub fn check(&self, client: &MiningBlockChainClient, tx: &SignedTransaction) -> Result<bool, String> {
		debug_assert_eq!(tx.gas_price, U256::zero());

		if let Some(ref contract) = *self.contract.lock() {
			let do_call = |a, d| client.call_contract(a, d);
			contract.certified(&do_call, &tx.sender())
		} else {
			Err("contract is not configured".to_owned())
		}
	}
}

mod provider {
	// Autogenerated from JSON contract definition using Rust contract convertor.
	// Command line: --jsonabi=SimpleCertifier.abi --explicit-do-call
	#![allow(unused_imports)]
	use std::string::String;
	use std::result::Result;
	use std::fmt;
	use {util, ethabi};
	use util::{FixedHash, Uint};

	pub struct Contract {
		contract: ethabi::Contract,
		address: util::Address,
		
	}
	impl Contract {
		pub fn new(address: util::Address) -> Self
			{
			Contract {
				contract: ethabi::Contract::new(ethabi::Interface::load(b"[{\"constant\":false,\"inputs\":[{\"name\":\"_new\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_who\",\"type\":\"address\"}],\"name\":\"certify\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_who\",\"type\":\"address\"},{\"name\":\"_field\",\"type\":\"string\"}],\"name\":\"getAddress\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_who\",\"type\":\"address\"}],\"name\":\"revoke\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"delegate\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_who\",\"type\":\"address\"},{\"name\":\"_field\",\"type\":\"string\"}],\"name\":\"getUint\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_new\",\"type\":\"address\"}],\"name\":\"setDelegate\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_who\",\"type\":\"address\"}],\"name\":\"certified\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_who\",\"type\":\"address\"},{\"name\":\"_field\",\"type\":\"string\"}],\"name\":\"get\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"payable\":false,\"type\":\"function\"}]").expect("JSON is autogenerated; qed")),
				address: address,
				
			}
		}
		fn as_string<T: fmt::Debug>(e: T) -> String { format!("{:?}", e) }
		
		/// Auto-generated from: `{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"}`
		#[allow(dead_code)]
		pub fn set_owner<F>(&self, do_call: &F, _new: &util::Address) -> Result<(), String>
			where F: Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send  {
			let call = self.contract.function("setOwner".into()).map_err(Self::as_string)?;
			let data = call.encode_call(
				vec![ethabi::Token::Address(_new.clone().0)]
			).map_err(Self::as_string)?;
			call.decode_output((do_call)(self.address.clone(), data)?).map_err(Self::as_string)?;
			
			Ok(())
		}

		/// Auto-generated from: `{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"certify","outputs":[],"payable":false,"type":"function"}`
		#[allow(dead_code)]
		pub fn certify<F>(&self, do_call: &F, _who: &util::Address) -> Result<(), String>
			where F: Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send  {
			let call = self.contract.function("certify".into()).map_err(Self::as_string)?;
			let data = call.encode_call(
				vec![ethabi::Token::Address(_who.clone().0)]
			).map_err(Self::as_string)?;
			call.decode_output((do_call)(self.address.clone(), data)?).map_err(Self::as_string)?;
			
			Ok(())
		}

		/// Auto-generated from: `{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"}`
		#[allow(dead_code)]
		pub fn get_address<F>(&self, do_call: &F, _who: &util::Address, _field: &str) -> Result<util::Address, String>
			where F: Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send  {
			let call = self.contract.function("getAddress".into()).map_err(Self::as_string)?;
			let data = call.encode_call(
				vec![ethabi::Token::Address(_who.clone().0), ethabi::Token::String(_field.to_owned())]
			).map_err(Self::as_string)?;
			let output = call.decode_output((do_call)(self.address.clone(), data)?).map_err(Self::as_string)?;
			let mut result = output.into_iter().rev().collect::<Vec<_>>();
			Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_address().ok_or("Invalid type returned")?; util::Address::from(r) }))
		}

		/// Auto-generated from: `{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"revoke","outputs":[],"payable":false,"type":"function"}`
		#[allow(dead_code)]
		pub fn revoke<F>(&self, do_call: &F, _who: &util::Address) -> Result<(), String>
			where F: Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send  {
			let call = self.contract.function("revoke".into()).map_err(Self::as_string)?;
			let data = call.encode_call(
				vec![ethabi::Token::Address(_who.clone().0)]
			).map_err(Self::as_string)?;
			call.decode_output((do_call)(self.address.clone(), data)?).map_err(Self::as_string)?;
			
			Ok(())
		}

		/// Auto-generated from: `{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"}`
		#[allow(dead_code)]
		pub fn owner<F>(&self, do_call: &F) -> Result<util::Address, String>
			where F: Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send  {
			let call = self.contract.function("owner".into()).map_err(Self::as_string)?;
			let data = call.encode_call(
				vec![]
			).map_err(Self::as_string)?;
			let output = call.decode_output((do_call)(self.address.clone(), data)?).map_err(Self::as_string)?;
			let mut result = output.into_iter().rev().collect::<Vec<_>>();
			Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_address().ok_or("Invalid type returned")?; util::Address::from(r) }))
		}

		/// Auto-generated from: `{"constant":true,"inputs":[],"name":"delegate","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"}`
		#[allow(dead_code)]
		pub fn delegate<F>(&self, do_call: &F) -> Result<util::Address, String>
			where F: Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send  {
			let call = self.contract.function("delegate".into()).map_err(Self::as_string)?;
			let data = call.encode_call(
				vec![]
			).map_err(Self::as_string)?;
			let output = call.decode_output((do_call)(self.address.clone(), data)?).map_err(Self::as_string)?;
			let mut result = output.into_iter().rev().collect::<Vec<_>>();
			Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_address().ok_or("Invalid type returned")?; util::Address::from(r) }))
		}

		/// Auto-generated from: `{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"}`
		#[allow(dead_code)]
		pub fn get_uint<F>(&self, do_call: &F, _who: &util::Address, _field: &str) -> Result<util::U256, String>
			where F: Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send  {
			let call = self.contract.function("getUint".into()).map_err(Self::as_string)?;
			let data = call.encode_call(
				vec![ethabi::Token::Address(_who.clone().0), ethabi::Token::String(_field.to_owned())]
			).map_err(Self::as_string)?;
			let output = call.decode_output((do_call)(self.address.clone(), data)?).map_err(Self::as_string)?;
			let mut result = output.into_iter().rev().collect::<Vec<_>>();
			Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_uint().ok_or("Invalid type returned")?; util::U256::from(r.as_ref()) }))
		}

		/// Auto-generated from: `{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setDelegate","outputs":[],"payable":false,"type":"function"}`
		#[allow(dead_code)]
		pub fn set_delegate<F>(&self, do_call: &F, _new: &util::Address) -> Result<(), String>
			where F: Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send  {
			let call = self.contract.function("setDelegate".into()).map_err(Self::as_string)?;
			let data = call.encode_call(
				vec![ethabi::Token::Address(_new.clone().0)]
			).map_err(Self::as_string)?;
			call.decode_output((do_call)(self.address.clone(), data)?).map_err(Self::as_string)?;
			
			Ok(())
		}

		/// Auto-generated from: `{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}`
		#[allow(dead_code)]
		pub fn certified<F>(&self, do_call: &F, _who: &util::Address) -> Result<bool, String>
			where F: Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send  {
			let call = self.contract.function("certified".into()).map_err(Self::as_string)?;
			let data = call.encode_call(
				vec![ethabi::Token::Address(_who.clone().0)]
			).map_err(Self::as_string)?;
			let output = call.decode_output((do_call)(self.address.clone(), data)?).map_err(Self::as_string)?;
			let mut result = output.into_iter().rev().collect::<Vec<_>>();
			Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r }))
		}

		/// Auto-generated from: `{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"}`
		#[allow(dead_code)]
		pub fn get<F>(&self, do_call: &F, _who: &util::Address, _field: &str) -> Result<util::H256, String>
			where F: Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send  {
			let call = self.contract.function("get".into()).map_err(Self::as_string)?;
			let data = call.encode_call(
				vec![ethabi::Token::Address(_who.clone().0), ethabi::Token::String(_field.to_owned())]
			).map_err(Self::as_string)?;
			let output = call.decode_output((do_call)(self.address.clone(), data)?).map_err(Self::as_string)?;
			let mut result = output.into_iter().rev().collect::<Vec<_>>();
			Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_fixed_bytes().ok_or("Invalid type returned")?; util::H256::from_slice(r.as_ref()) }))
		}
	}
}
