// Contract: MerchantPool
// Target function: function consume2(address _merchant, address _serviceProvider, uint256 _amount, uint256 time, bytes memory signature) public
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
        return true;
    }

    /**
     * @dev Moves tokens `amount` from `sender` to `recipient`.
     *
     * This is internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    function _transfer(address sender, address recipient, uint256 amount) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(sender, recipient, amount);

        _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
        _balances[recipient] = _balances[recipient].add(amount);
        emit Transfer(sender, recipient, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements
     *
     * - `to` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply = _totalSupply.add(amount);
        _balances[account] = _balances[account].add(amount);
        emit Transfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
        _totalSupply = _totalSupply.sub(amount);
        emit Transfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev Sets {decimals} to a value other than the default one of 18.
     *
     * WARNING: This function should only be called from the constructor. Most
     * applications that interact with token contracts will not expect
     * {decimals} to ever change, and may work incorrectly if it does.
     */
    function _setupDecimals(uint8 decimals_) internal {
        _decimals = decimals_;
    }

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be to transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
}

interface HPPMerchantProof {
    function mint(address _to, uint256 _amount) external;
    function burn(address _account, uint256 _amount) external;
    function transfer(address recipient, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

interface HPPToken {
    function mint(address to, uint256 _amount) external;
    function burn(uint256 _amount) external;
    function transfer(address recipient, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
    function canMint(uint256 _amount) external view returns (bool);
}

contract MerchantPool is Ownable, Initializable {

    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    // Info of each user.
    struct UserInfo {
        uint256 amount;     // How many LP tokens the user has provided.
        uint256 rewardDebt; // Reward debt. See explanation below.
        //
        // We do some fancy math here. Basically, any point in time, the amount of token
        // entitled to a user but is pending to be distributed is:
        //
        //   pending reward = (user.amount * pool.accPerShare) - user.rewardDebt
        //
        // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens:
        //   1. The pool's `accPerShare` (and `lastRewardBlock`) gets updated.
        //   2. User receives the pending reward sent to his/her address.
        //   3. User's `amount` gets updated.
        //   4. User's `rewardDebt` gets updated.
    }

    struct MerchantInfo {
        uint256 sid;
        string name;
        string addr;
        string desc;
        int256 longitude;
        int256 latitude;
        address salesman;
        // Does the merchant support the burn of a part of consumption when receiving payment, in order to gain mining speed growth.
        // Open by default, you can choose to close.
        bool supportBurn;
        uint256 status;         // 1=normal, 2=deleted
    }

    struct SalesmanInfo {
        uint256 salesmanMargin;
        uint256 status;         // 1=normal, 2=cancel, 3=punish
    }

    uint256 constant DENOMINATOR = 100;
    uint256 public MIN_BLOCK_REWARD = 2 * 10**18;

    HPPToken public token;
    uint256 public perBlock;
    uint256 public reduceBlockCount;
    uint256 public lastReduceBlock;
    uint256 public reduceRate;
    uint256 public startBlock;

    HPPMerchantProof public lpToken;           // Address of LP token contract.
    uint256 public lastRewardBlock;  // Last block number that token distribution occurs.
    uint256 public accPerShare;    // Accumulated token per share, times 1e12. See below.

    uint256 public salesmanMargin;  // Deposit required to become a salesman
    uint256 public salesmanRewardRatio; // Percentage of salesman earning the merchant’s mining share
    uint256 public consumptionDiscountRatio; // Discount rate for user purchase, the default is 10%
    uint256 public consumptionBurnRatio;    // The proportion of the amount of money that is destroyed when merchant collection, the default is 8%
    uint256 public serviceProviderRatio;     // The proportion of the collection share obtained by the operating service party, the default is 2%
    address public master;
    address public admin;
    mapping (address => SalesmanInfo) public salesmanInfo;
    mapping (address => MerchantInfo) public merchantInfo;
    // Info of each user that stakes LP tokens.
    mapping (address => UserInfo) public userInfo;
    address[] public merchantArray;
    mapping(bytes32 => bool) private consumeHashes;

    event Deposit(address indexed user, uint256 amount);
    event Withdraw(address indexed user, uint256 amount);
    event EmergencyWithdraw(address indexed user, uint256 amount);
    event RegisterSalesman(address indexed user);
    event RegisterMerchant(address indexed salesmanAddress, address indexed merchant, string name);
    event CancelSalesman(address indexed salesmanAddress);
    event RemoveMerchant(address indexed merchant);
    event PunishSalesman(address indexed salesmanAddress, address[] indexed merchants);
    event ChangeMerchantAddress(address indexed oldAddress, address indexed newAddress);
    event ChangeMerchantInfo(address indexed merchant);
    event Consume(address indexed user, address indexed merchant, address serviceProvider, uint256 indexed amount,
        uint256 merchantReceiveAmount, uint256 userDiscountAmount, uint256 serviceProviderAmount, uint256 burnAmount);

    modifier onlyMaster() {
        require(_msgSender() == master, "Only master.");
        _;
    }

    modifier onlyAdmin() {
        require(_msgSender() == admin, "Only admin.");
        _;
    }

    modifier onlySalesman() {
        require(salesmanInfo[_msgSender()].status == 1, "Only salesman.");
        _;
    }

    modifier onlyMerchant() {
        require(merchantInfo[_msgSender()].status == 1, "Only merchant.");
        _;
    }

    function initialize(HPPToken _token, uint256 _perBlock, uint256 _startBlock, uint256 _reduceBlockCount, HPPMerchantProof _lpToken, uint256 _salesmanMargin, address _master) public onlyOwner initializer {
        token = _token;
        perBlock = _perBlock;
        startBlock = _startBlock;
        reduceBlockCount = _reduceBlockCount;
        lastRewardBlock = block.number > startBlock ? block.number : _startBlock;
        lastReduceBlock = _startBlock;
        reduceRate = 3;

        lpToken = _lpToken;
        master = _master;
        admin = _master;
        salesmanMargin = _salesmanMargin;
        salesmanRewardRatio  = 10; // 10%
        consumptionDiscountRatio = 10;  // 10%
        consumptionBurnRatio = 8;      // 8%
        serviceProviderRatio = 2;  // 2%
    }

    function registerSalesman() public {
        address salesmanAddress = _msgSender();
        SalesmanInfo storage salesman = salesmanInfo[salesmanAddress];
        // Salesman who have been cancelled or punished can register again
        require(salesman.status != 1, "Cannot register twice.");
        uint256 bal = token.balanceOf(salesmanAddress);
        require(bal >= salesmanMargin, "Insufficient margin.");
        token.transferFrom(salesmanAddress, address(this), salesmanMargin);
        salesman.status = 1;
        salesman.salesmanMargin = salesmanMargin;

        emit RegisterSalesman(salesmanAddress);
    }

    function cancelSalesman() public onlySalesman {
        address salesmanAddress = _msgSender();
        SalesmanInfo storage salesman = salesmanInfo[salesmanAddress];
        require(salesman.status == 1, "The address is not salesman.");
        safeTokenTransfer(salesmanAddress, salesman.salesmanMargin);
        salesman.status = 2;
        salesman.salesmanMargin = 0;

        emit CancelSalesman(salesmanAddress);
    }

    function registerMerchant(address _address, string memory _name, string memory _desc, string memory _addr, int256 _longitude, int256 _latitude) public onlySalesman {
        MerchantInfo storage merchant = merchantInfo[_address];
        require(merchant.status == 0, "The merchant is already registered");
        merchant.sid = merchantArray.length;
        merchant.name = _name;
        merchant.desc = _desc;
        merchant.addr = _addr;
        merchant.longitude = _longitude;
        merchant.latitude = _latitude;
        merchant.salesman = _msgSender();
        merchant.supportBurn = true;
        merchant.status = 1;

        merchantArray.push(_address);
        lpToken.mint(_address, 10**18);
        emit RegisterMerchant(merchant.salesman, _address, _name);
    }

    function punishSalesman(address _salesmanAddress, address[] memory merchants) public onlyAdmin {
        SalesmanInfo storage salesman = salesmanInfo[_salesmanAddress];
        require(salesman.status == 1, "The address is not salesman.");
        burnToken(salesman.salesmanMargin);
        salesman.status = 3;
        salesman.salesmanMargin = 0;

        for (uint i = 0; i < merchants.length; i++) {
            address merchantAddress = merchants[i];
            UserInfo storage user = userInfo[merchantAddress];
            lpToken.burn(address(this), user.amount);
            user.amount = 0;
            user.rewardDebt = 0;
            delete userInfo[merchantAddress];

            MerchantInfo storage merchant = merchantInfo[merchantAddress];
            require(merchant.salesman == _salesmanAddress, "Merchant and salesman mismatch.");
            merchant.status = 2;

            emit RemoveMerchant(merchantAddress);
        }
        updatePool();
        emit PunishSalesman(_salesmanAddress, merchants);
    }

    function deleteMerchant(address _merchant) public onlyAdmin {
        UserInfo storage user = userInfo[_merchant];
        lpToken.burn(address(this), user.amount);
        user.amount = 0;
        user.rewardDebt = 0;
        delete userInfo[_merchant];

        MerchantInfo storage merchant = merchantInfo[_merchant];
        merchant.status = 2;

        updatePool();
        emit RemoveMerchant(_merchant);
    }

    function changeAdmin(address _admin) public onlyAdmin {
        admin = _admin;
    }

    function changeMaster(address _master) public onlyMaster {
        master = _master;
    }

    function changeSalesmanMargin(uint256 _salesmanMargin) public onlyMaster {
        salesmanMargin = _salesmanMargin;
    }

    function changeSalesmanRewardRatio(uint256 _salesmanRewardRatio) public onlyMaster {
        require(_salesmanRewardRatio >= 0 && _salesmanRewardRatio <= 20, "salesmanRewardRatio need >= 0 and <= 20");
        salesmanRewardRatio = _salesmanRewardRatio;
    }

    function changeConsumptionDiscountRatio(uint256 _consumptionDiscountRatio) public onlyMaster {
        require(_consumptionDiscountRatio >= 0 && _consumptionDiscountRatio <= 50, "consumptionDiscountRatio need >= 0 and <= 50");
        consumptionDiscountRatio = _consumptionDiscountRatio;
    }

    function changeConsumptionBurnRatio(uint256 _consumptionBurnRatio) public onlyMaster {
        require(_consumptionBurnRatio >= 0 && _consumptionBurnRatio <= 20, "consumptionBurnRatio need >= 0 and <= 20");
        consumptionBurnRatio = _consumptionBurnRatio;
    }

    function changeServiceProviderRatio(uint256 _serviceProviderRatio) public onlyMaster {
        require(_serviceProviderRatio >= 0 && _serviceProviderRatio <= 10, "serviceProviderRatio need >= 0 and <= 10");
        serviceProviderRatio = _serviceProviderRatio;
    }

    function changeReduce(uint256 _reduceBlockCount, uint256 _reduceRate) public onlyMaster {
        reduceBlockCount = _reduceBlockCount;
        reduceRate = _reduceRate;
    }

    function changeMerchantAddress(address _address) public onlyMerchant {
        require(merchantInfo[_address].status == 0, "Duplicate merchant address.");
        MerchantInfo storage merchant = merchantInfo[msg.sender];
        merchantInfo[_address] = merchant;
        merchantArray[merchant.sid] = _address;
        delete merchantInfo[msg.sender];

        UserInfo storage user = userInfo[msg.sender];
        userInfo[_address] = user;
        delete userInfo[msg.sender];

        emit ChangeMerchantAddress(msg.sender, _address);
    }

    function changeMerchantInfo(string memory _name, string memory _desc, string memory _addr, int256 _longitude, int256 _latitude) public onlyMerchant {
        MerchantInfo storage merchant = merchantInfo[msg.sender];
        merchant.name = _name;
        merchant.desc = _desc;
        merchant.addr = _addr;
        merchant.longitude = _longitude;
        merchant.latitude = _latitude;

        emit ChangeMerchantInfo(msg.sender);
    }

    function changeBurnFlag(bool supportBurn) public onlyMerchant {
        MerchantInfo storage merchant = merchantInfo[msg.sender];
        merchant.supportBurn = supportBurn;
    }

    // Consumption pay method, product realization end guides users to call this method
    function consume1(address _merchant, address _serviceProvider, uint256 _amount) public {
        consume(msg.sender, _merchant, _serviceProvider, _amount);
    }