contract MarketplaceControllerElectron is MarketplaceControllerCommon, DigixConstantsExtras, DigixConstantsElectron {

    function MarketplaceControllerElectron(address _resolver) public
    {
        require(init(CONTRACT_CONTROLLER_MARKETPLACE, _resolver));
    }

    struct UintData {
        uint256 pre;
        uint256 post;
    }

    struct Config {
        uint256 minimum_purchase_dgx_ng;
        uint256 maximum_block_drift;
        address eth_payment_collector;
        address token_payment_collector;

        uint256 max_dgx_available_daily;
        uint256 price_floor_wei_per_dgx_mg;
    }

    struct Verification {
        uint256 token_of_signer;
        uint256 signer_min_order;
    }

    struct Purchase {
        uint256 amount_dgx_ng;
        uint256 token_index;
        User buyer;
        Verification verification;
        Config config;
        uint256 payment_sent;
        uint256 price;
        uint256 block_number;
        address signer;
        bytes signature;
    }

    struct User {
        address account;
        uint256 kyc_tier;
        uint256 kyc_expiry;
        uint256 total_purchased_today;
        uint256 lifetime_limit;
        uint256 daily_limit;
        uint256 lifetime_total_purchased;
        address next_address;
    }

    function marketplace_storage_electron()
        internal
        constant
        returns (MarketplaceStorageElectron _contract)
    {
        _contract = MarketplaceStorageElectron(get_contract(CONTRACT_STORAGE_MARKETPLACE_ELECTRON));
    }

    function verification_service()
        internal
        constant
        returns (VerificationService _contract)
    {
        _contract = VerificationService(get_contract(CONTRACT_SERVICE_VERIFICATION));
    }

    function identity()
        internal
        constant
        returns (Identity _contract)
    {
        _contract = Identity(get_contract(CONTRACT_INTERACTIVE_IDENTITY));
    }

    function token_contract(uint256 _token_index)
        internal
        constant
        returns (ERCTwenty _token_contract)
    {
        _token_contract = ERCTwenty(marketplace_storage_electron().all_tokens(_token_index));
    }


    function purchase_with_eth(
        uint256 _wei_sent,
        address _buyer,
        uint256 _kyc_tier,
        uint256 _kyc_expiry,
        uint256 _block_number,
        uint256 _wei_per_dgx_mg,
        address _signer,
        bytes _signature
    )
        if_sender_is(CONTRACT_INTERACTIVE_MARKETPLACE_ELECTRON)
        payable
        public
        returns (bool _success, uint256 _purchased_amount)
    {
        _purchased_amount = process_purchase_onchain(
            _wei_sent,
            _buyer,
            _kyc_tier,
            _kyc_expiry,
            _block_number,
            _wei_per_dgx_mg,
            _signer,
            TOKEN_INDEX_ETHER,
            _signature
        );
        _success = true;
    }

    function purchase_with_token(
        uint256 _tokens_sent,
        address _buyer,
        uint256 _kyc_tier,
        uint256 _kyc_expiry,
        uint256 _block_number,
        uint256 _token_per_1000ton,
        address _signer,
        uint256 _token_index,
        bytes _signature
    )
        if_sender_is(CONTRACT_INTERACTIVE_MARKETPLACE_ELECTRON)
        public
        returns (bool _success, uint256 _purchased_amount)
    {
        // Before calling this, user needs to approve this MarketplaceControllerElectron contract to withdraw the ERC20 token
        require(token_contract(_token_index).allowance(_buyer, address(this)) >= _tokens_sent);
        require(_token_index != TOKEN_INDEX_FIAT); // must not be a fiat purchase

        _purchased_amount = process_purchase_onchain(
            _tokens_sent,
            _buyer,
            _kyc_tier,
            _kyc_expiry,
            _block_number,
            _token_per_1000ton,
            _signer,
            _token_index,
            _signature
        );
        _success = true;
    }

    // to be called by ROLE_ELECTRON_MARKETPLACE_ADMIN to confirm a user purchase using fiat
    function add_purchase_with_fiat(
        address _caller,
        address _buyer,
        uint256 _kyc_tier,
        uint256 _kyc_expiry,
        uint256 _amount_dgx_ng
    )
        if_sender_is(CONTRACT_INTERACTIVE_MARKETPLACE_ELECTRON)
        if_caller_is_role(_caller, ROLE_ELECTRON_MARKETPLACE_ADMIN)
        public
    {
        Purchase memory _purchase = get_basic_purchase_object(_buyer, _kyc_tier, _kyc_expiry, TOKEN_INDEX_FIAT);
        _purchase.amount_dgx_ng = _amount_dgx_ng;

        Purchase memory _processed;
        _processed = verify_purchase_limits(_purchase);
        update_purchase_in_storages(_processed);
    }

    function process_purchase_onchain(
        uint256 _payment_sent,
        address _buyer,
        uint256 _kyc_tier,
        uint256 _kyc_expiry,
        uint256 _block_number,
        uint256 _price,
        address _signer,
        uint256 _token_index,
        bytes _signature
    )
        internal
        returns (uint256 _purchase_amount)
    {
        // get basic common information required for all purchase types
        Purchase memory _purchase = get_basic_purchase_object(_buyer, _kyc_tier, _kyc_expiry, _token_index);

        // more information that will only be required for
        // purchases done using ETH or ERC-20 tokens
        _purchase.payment_sent = _payment_sent;
        _purchase.price = _price;
        _purchase.block_number = _block_number;
        _purchase.signer = _signer;
        _purchase.signature = _signature;

        // get information about the price signer
        _purchase.verification.token_of_signer = marketplace_storage_electron().token_of_signer(_signer);
        _purchase.verification.signer_min_order = marketplace_storage_electron().min_order_of_signer(_signer);

        if (_token_index > TOKEN_INDEX_ETHER) {
            // amount is:  (amount of tokens * 10^18) / (token_per_1000_ton * 1000_TON_TO_NG_which_is_10^18)
            // if this ERC-20 token doesn't have 18 decimal places, then we take care of that by
            // taking the ratio of (10^18 / 10^(decimals_in_that_token))
            // (for instance, USDT has only 6 decimal places)
            // which is actually:  (amount of tokens) / (token_per_1000_ton)
            _purchase.amount_dgx_ng = (_purchase.payment_sent * (10 ** 18)) / (_purchase.price * (10 ** token_contract(_token_index).decimals()));
        } else {
            // amount is the wei_sent * MILLIGRAM_TO_NANOGRAMS / (price in terms of wei_per_dgx_mg)
            _purchase.amount_dgx_ng = (_purchase.payment_sent * MILLIGRAM_TO_NANOGRAMS) / _purchase.price;
            _purchase.config.price_floor_wei_per_dgx_mg = marketplace_storage().read_price_floor();
            require(_purchase.price >= _purchase.config.price_floor_wei_per_dgx_mg);
        }

        Purchase memory _processed;

        // verifies purchase limits as well as KYC information
        _processed = verify_purchase_limits(verify_purchase_onchain(_purchase));

        // update information in the storage contracts
        update_purchase_in_storages(_processed);

        if (_token_index > TOKEN_INDEX_ETHER) {
            // this is a token purchase
            _purchase.config.token_payment_collector = marketplace_storage_electron().token_payment_collector(_token_index);
            require(
                token_contract(_token_index)
                .transferFrom(
                    _purchase.buyer.account,
                    _purchase.config.token_payment_collector,
                    _purchase.payment_sent
                )
            );
        } else {
            // this is an ETH purchase
            _processed.config.eth_payment_collector.transfer(_purchase.payment_sent);
        }

        _purchase_amount = _purchase.amount_dgx_ng;
    }

    function get_basic_purchase_object(address _buyer, uint256 _kyc_tier, uint256 _kyc_expiry, uint256 _token_index)
        internal
        constant
        returns (Purchase memory)
    {
        // common information required to be checked for all kinds of purchases
        Purchase memory _purchase;
        (, _purchase.config.minimum_purchase_dgx_ng, _purchase.config.maximum_block_drift, _purchase.config.eth_payment_collector) = marketplace_storage().read_config();
        _purchase.config.max_dgx_available_daily = marketplace_storage().read_max_dgx_available_daily();
        _purchase.buyer.account = _buyer;
        _purchase.buyer.kyc_tier = _kyc_tier;
        _purchase.buyer.kyc_expiry = _kyc_expiry;
        _purchase.token_index = _token_index;
        return _purchase;
    }

    function update_purchase_in_storages(Purchase memory _processed)
        internal
    {
        // update the marketplace storage contract
        require(marketplace_storage().update_user_purchase(
            _processed.buyer.account,
            _processed.buyer.account,
            _processed.amount_dgx_ng,
            _processed.price,
            _processed.block_number,
            DUMMY_NONCE)
        );

        // update the marketplace storage electron contract
        // this contract stores information regarding which token was used
        // for the purchase, and updates the buyer's information
        marketplace_storage_electron().add_purchase(
            _processed.buyer.account,
            marketplace_storage().read_total_number_of_purchases() - 1,
            _processed.token_index,
            _processed.amount_dgx_ng
        );
    }

    function verify_purchase_onchain(Purchase memory _purchase)
        internal
        constant
        returns (Purchase memory _processed)
    {
        _processed = _purchase;

        // the pricefeed signer should be a valid signer for the purchase token
        require(_purchase.verification.token_of_signer == _purchase.token_index);
        // the number of DGX bought should be greater than the minimum DGX for the signer's price level
        require(_purchase.amount_dgx_ng >= _purchase.verification.signer_min_order);

        // verify all arguments of the signed pricefeed
        require(
            verification_service().verify_signed_price(
                _purchase.buyer.account,
                _purchase.buyer.kyc_tier,
                _purchase.buyer.kyc_expiry,
                _purchase.block_number,
                _purchase.price,
                _purchase.signature,
                _purchase.signer
            )
        );

        // the current block number should be within the threshold
        require((_processed.block_number + _purchase.config.maximum_block_drift) >= block.number);
    }

    function verify_purchase_limits(Purchase memory _purchase)
        internal
        constant
        returns (Purchase memory _processed)
    {
        _processed = _purchase;

        // collect information about the user's limits
        (
            ,, _processed.buyer.lifetime_total_purchased,
            , _processed.buyer.next_address
        ) = marketplace_storage_electron().read_user(_processed.buyer.account);
        (,_processed.buyer.total_purchased_today) = marketplace_storage().read_user(_processed.buyer.account);

        // get user's limits depending on the tier. Note that Kyc valid = tier 2, not valid = tier 1
        (_processed.buyer.daily_limit, _processed.buyer.lifetime_limit) = get_user_limits(
            _processed.buyer.account,
            _processed.buyer.kyc_tier,
            _processed.buyer.kyc_expiry
        );

        // the user should be either in tier 1 (with additional checks on limits)
        // or any upper kyc tier with an expiry beyond time now
        require(
            (_processed.buyer.kyc_tier == 1) ||
            (_processed.buyer.kyc_expiry > now)
        );
        // including this purchase, the total DGXs purchased today should be within max available for purchase daily
        require(marketplace_storage().read_total_global_purchased_today() + _processed.amount_dgx_ng <= _processed.config.max_dgx_available_daily);
        // number of DGXs to be purchased should be more than the minimum purchase amount
        require(_processed.amount_dgx_ng >= _processed.config.minimum_purchase_dgx_ng);
        // including this purchase, the total DGXs purchased today by this user should be within their daily purchase limits
        require((_processed.buyer.total_purchased_today + _processed.amount_dgx_ng) <= _processed.buyer.daily_limit);
        // including this purchase, the total DGXs purchased by this user should be within their lifetime purchase limits
        require((_processed.buyer.lifetime_total_purchased + _processed.amount_dgx_ng) <= _processed.buyer.lifetime_limit);
        // This user address must not have been replaced
        require(_processed.buyer.next_address == address(0x0));
    }

    function get_user_limits(address _user, uint256 _kyc_tier, uint256 _kyc_expiry)
        public
        constant
        returns (
            uint256 _daily_limit,
            uint256 _lifetime_limit
        )
    {
        // get DGX purchase limits for this user
        (
            _lifetime_limit,
            _daily_limit,,,
        ) = marketplace_storage_electron().read_user(_user);

        // check if user KYC is valid
        bool _valid_kyc = ((_kyc_tier > 1) && (_kyc_expiry > now));

        if (!_valid_kyc) {
            // if the user is in tier 1
            _daily_limit = max(_daily_limit, marketplace_storage_electron().default_tier1_daily_limit());
            _lifetime_limit = max(_lifetime_limit, marketplace_storage_electron().default_tier1_lifetime_limit());
        } else {
            // this user is in tier 2
            _daily_limit = max(_daily_limit, marketplace_storage_electron().default_tier2_daily_limit());
            _lifetime_limit = MAX_INTEGER;
        }
    }

    function max(uint a, uint b)
        internal
        pure
        returns (uint)
    {
        if (a < b) {
            return b;
        }
        return a;
    }
}
/// @title DigixCore Marketplace Controller
/// @author DigixGlobal Pte Ltd
function add_purchase_with_fiat(
    address _caller,
    address _buyer,
    uint256 _kyc_tier,
    uint256 _kyc_expiry,
    uint256 _amount_dgx_ng
)
    if_sender_is(CONTRACT_INTERACTIVE_MARKETPLACE_ELECTRON)
    if_caller_is_role(_caller, ROLE_ELECTRON_MARKETPLACE_ADMIN)
    public
{
    Purchase memory _purchase = get_basic_purchase_object(_buyer, _kyc_tier, _kyc_expiry, TOKEN_INDEX_FIAT);
    _purchase.amount_dgx_ng = _amount_dgx_ng;

    Purchase memory _processed;
    _processed = verify_purchase_limits(_purchase);
    update_purchase_in_storages(_processed);
}

// to be called by ROLE_ELECTRON_MARKETPLACE_ADMIN to confirm a user purchase using fiat