// SPDX-License-Identifier: MIT
pragma solidity =0.8.4;

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "./interfaces/IPriceConsumer.sol";

contract PriceConsumer is IPriceConsumer {
	/**
	 * Network: Rinkeby
	 * Aggregators: ETH/USD, BTC/USD, XAU/USD, XFT/USD
	 */

	enum Assets {
		XFT,
		USD,
		BTC,
		ETH,
		XAU
	}

	uint8 internal constant _decimalmax = 18;

	constructor() {}

	/**
	 * @dev Returns required input amount of the asset given an output amount of other asset.
	 * @param exchAggr aggregator of input asset.
	 * @param quoteAggr aggregator of output asset.
	 * @param exchAmount output amount.
	 * @return input amount and decimals.
	 */
	function exchangeAssets(
		uint8 exchAggr,
		uint8 quoteAggr,
		uint256 exchAmount
	) external view override returns (uint256, uint8) {
		require(exchAggr != quoteAggr, "Aggregator names are the same");
		(uint256 _quotePrice, uint8 _quoteDecimal) = getDerivedPrice(exchAggr, quoteAggr);

		return (uint256(exchAmount * _quotePrice) / (uint256(10**_quoteDecimal)), _quoteDecimal);
	}

	/**
	 * @dev Returns output amount of the asset given other asset.
 	 * @param baseAggr input asset agregator.
	 * @param quoteAggr output asset agregator.
	 * @return amount and decimals.
	 */
	function getDerivedPrice(uint8 baseAggr, uint8 quoteAggr)
		public
		view
		returns (uint256, uint8)
	{
		(int256 _basePrice, uint8 _baseDecimals) = getLatestPrice(baseAggr);
		(int256 _quotePrice, uint8 _quoteDecimals) = getLatestPrice(quoteAggr);

		(_basePrice, _baseDecimals) = _scalePrice(_basePrice, _baseDecimals, _decimalmax);
		(_quotePrice, _quoteDecimals) = _scalePrice(_quotePrice, _quoteDecimals, _decimalmax);

		return (
			uint256((_basePrice * (int256(10**uint256(_baseDecimals)))) / _quotePrice),
			_quoteDecimals
		);
	}

	/**
	 * @dev Returns the latest price.
	 * @param aggregator Token name by number in Assets enum.
	 * @return price and decimals
	 */
	function getLatestPrice(uint8 aggregator) public view returns (int256, uint8) {
		if (aggregator == uint8(Assets.USD)) {
			return (int256(10**8), 8);
		}
		address _addrAggr = _getAddrAggregator(aggregator);
		require(_addrAggr != address(0), "Adress aggregator cannot be zero.");
		(, int256 _price, , , ) = AggregatorV3Interface(_addrAggr).latestRoundData();
		uint8 _decimals = AggregatorV3Interface(_addrAggr).decimals();
		return (_price, _decimals);
	}

	/**
	 * @dev Returns the aggregator address.
	 * @param _aggregator Token name by number in Assets enum.
	 * @return address.
	 */
	function _getAddrAggregator(uint8 _aggregator) internal pure returns (address) {
		address _addrAggregator;

		if (_aggregator == uint8(Assets.BTC))
			_addrAggregator = 0xECe365B379E1dD183B20fc5f022230C044d51404;

		if (_aggregator == uint8(Assets.ETH))
			_addrAggregator = 0x8A753747A1Fa494EC906cE90E9f37563A8AF630e;

		if (_aggregator == uint8(Assets.XAU))
			_addrAggregator = 0x81570059A0cb83888f1459Ec66Aad1Ac16730243;

		if (_aggregator == uint8(Assets.XFT))
			_addrAggregator = 0xab4a352ac35dFE83221220D967Db41ee61A0DeFa;

		return _addrAggregator;
	}

	/**
	 * @dev Convert the price to the set decimals.
	 * @param _price Token name by number in Assets enum.
	 * @param _priceDecimals Token name by number in Assets enum.
	 * @param _decimals Token name by number in Assets enum.
	 * @return price and decimals.
	 */
	function _scalePrice(
		int256 _price,
		uint8 _priceDecimals,
		uint8 _decimals
	) internal pure returns (int256, uint8) {
		if (_priceDecimals < _decimals) {
			return (_price * int256(10**uint256(_decimals - _priceDecimals)), _decimals);
		} else if (_priceDecimals > _decimals) {
			return (_price / int256(10**uint256(_priceDecimals - _decimals)), _priceDecimals);
		}
		return (_price, _priceDecimals);
	}

	/**
	 * @dev Function for check aggregators equality.
	 * @param _aggr Token name by number in Assets enum.
	 * @param _name Token name by number in Assets enum.
	 * @return result of the equality check.
	 */
	function _isEqualAggr(string memory _aggr, string memory _name) internal pure returns (bool) {
		bool _isEq = bytes(_aggr).length == bytes(_name).length;
		for (uint8 i = 0; i < bytes(_aggr).length; i++) {
			_isEq = _isEq && (bytes(_aggr)[i] == bytes(_name)[i]);
		}
		return _isEq;
	}
}