contract TradeDSDCoupons is ReentrancyGuard {


    using SafeMath
    for uint;

    IDSDS public DSDS = IDSDS(0x6Bf977ED1A09214E6209F4EA5f525261f1A2690a);




    function DSDperUSDC() public view returns(uint) {
        return ERC20(0xBD2F0Cd039E0BFcf88901C98c0bFAc5ab27566e3).balanceOf(0x66e33d2605c5fB25eBb7cd7528E7997b0afA55E8).div(ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48).balanceOf(0x66e33d2605c5fB25eBb7cd7528E7997b0afA55E8)).div(1e9);
    } //to 3 decimals

    function USDCperETH() public view returns(uint) {
        return ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48).balanceOf(0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc).mul(1e12).div(ERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2).balanceOf(0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc));
    } //zero decimals

    function DSDperETH() public view returns(uint) {
        return DSDperUSDC().mul(USDCperETH()).div(1000);
    }



    address[] addressList; //each time an initial sellRate is set, address is added here
    mapping(address => uint) public sellRate;
    mapping(address => uint256) public ETHbalances; // records amounts invested  

    function setRate(uint rate) external nonReentrant {
        uint numElements = addressList.length;
        uint i;
        if (sellRate[msg.sender] == 0 && rate > 10 && rate < 100) { //initial rate for an address
            sellRate[msg.sender] = rate;
            addressList.push(msg.sender);
            numElements = addressList.length;
            //sort addressList in ascending rate order
            if (numElements > 1)
                for (i = numElements - 1; i > 0; i--) {
                    if (sellRate[addressList[i]] < sellRate[addressList[i - 1]]) {
                        addressList[i] = addressList[i - 1];
                        addressList[i - 1] = msg.sender;
                    } else break;
                }
        } else
            //update previous rate
            if (rate > 10 && rate < 100) {
                uint previousRate = sellRate[msg.sender];
                uint addressListLocation = 0;
                if (numElements > 1) {
                    for (i = numElements - 1; i > 0; i--) {
                        if (addressList[i] == msg.sender) {
                            addressListLocation = i;
                            break;
                        }
                    }
                    sellRate[msg.sender] = rate;
                    //sort addressList in ascending rate order  - case where previous rate was higher so shift down
                    if (previousRate > rate) {
                        for (i = addressListLocation; i > 0; i--) {
                            if (sellRate[addressList[i]] < sellRate[addressList[i - 1]]) {
                                addressList[i] = addressList[i - 1];
                                addressList[i - 1] = msg.sender;
                            } else break;
                        }
                    } else if (addressListLocation + 1 < numElements) //shift the other way
                    {
                        for (i = addressListLocation; i < numElements - 1; i++) {
                            if (sellRate[addressList[i]] > sellRate[addressList[i + 1]]) {
                                addressList[i] = addressList[i + 1];
                                addressList[i + 1] = msg.sender;
                            } else break;
                        }
                    }
                }

            }

    }



    function uint2str(uint i) internal pure returns(string) {
        if (i == 0) return "0";
        uint j = i;
        uint length;
        while (j != 0) {
            length++;
            j /= 10;
        }
        bytes memory bstr = new bytes(length);
        uint k = length - 1;
        while (i != 0) {
            bstr[k--] = byte(48 + i % 10);
            i /= 10;
        }
        return string(bstr);
    }




function address2ShortString(address _address) public pure returns(string memory) {
       bytes32 _bytes = bytes32(uint256(_address));
       bytes memory HEX = "0123456789abcdef";
       bytes memory _string = new bytes(42);
       _string[0] = '0';
       _string[1] = 'x';
       for(uint i = 0; i < 20; i++) {
           _string[2+i*2] = HEX[uint8(_bytes[i + 12] >> 4)];
           _string[3+i*2] = HEX[uint8(_bytes[i + 12] & 0x0f)];
       }

       bytes memory strBytes = bytes(string(_string));
       uint endIndex=4; uint startIndex=0;
       bytes memory result = new bytes(endIndex-startIndex);
       for(i = startIndex; i < endIndex; i++) {
        result[i-startIndex] = strBytes[i];}
       
       return string(result);
       
    }


    function showAvailableCoupons(uint maxPrice, uint addressListLocation) public view returns(string memory) { // 99999 location to show all addresses
        address addressToShow; uint numAddresses = 1;
        string memory couponList = "The following coupons are available to purchase directly with ETH at current DSD rates:";
        if (addressListLocation != 99999) {
            addressToShow = addressList[addressListLocation];
        } else {
            addressToShow = addressList[0];
            numAddresses = addressList.length;
        }
        uint DSDepoch = currentEpoch();
        for (uint i = 0; i < numAddresses; i++) {
            //break out if over maxPrice
            if (sellRate[addressToShow] > maxPrice) break;
            //first check if allowanceCoupons/sell is enabled in this addressToShow
            if (approvedToSell(addressToShow) > 1) { //loop through current epoch minus 360 to present
                couponList = string(abi.encodePacked(couponList, '{', address2ShortString(addressToShow),'..  DSD/Coupon offer: 0.', uint2str(sellRate[addressToShow]), ' }:'));
                for (uint ep = DSDepoch.sub(360); ep < DSDepoch; ep++) {
                    uint couponBalance = DSDS.balanceOfCoupons(addressToShow, ep);
                    if (couponBalance > 0) couponList = string(abi.encodePacked(couponList, '[', uint2str(ep), ': ', uint2str(couponBalance.div(1e18)), '] '));
                }

            }



            if (i + 1 < numAddresses) addressToShow = addressList[i + 1];
        }

        return couponList;


    }




    function buyCoupons(uint epoch, uint addressListLocation) payable external nonReentrant {
        uint buyRate = sellRate[addressList[addressListLocation]];
        address buyFrom = addressList[addressListLocation];
        uint refund;
        require(buyRate > 0, "Invalid address");
        require(approvedToSell(buyFrom) > 1, 'Sale not approved'); //require seller to have approved sale
        uint DSDETH = DSDperETH();
        uint couponAmount = msg.value.mul(DSDETH).mul(1e2).div(buyRate);
        uint couponBalance = DSDS.balanceOfCoupons(buyFrom, epoch);
        if (couponAmount > couponBalance) {refund = (couponAmount - couponBalance).mul(buyRate).div(DSDETH).div(1e2); couponAmount = couponBalance;} 
        if (refund > msg.value) refund = msg.value;

        ETHbalances[msg.sender] += refund;
        ETHbalances[buyFrom] += msg.value.sub(refund);
        DSDS.transferCoupons(buyFrom, msg.sender, epoch, couponAmount);

    }
    


    function approvedToSell(address addressToCheck) public view returns (uint) {
        return DSDS.allowanceCoupons(addressToCheck, address(this));  // anything over 1 means approved
    }
    
     
    function currentEpoch() public view returns (uint) {
        return DSDS.epoch();
    }

    function claimETH() external nonReentrant {
         uint claimFee=  ETHbalances[msg.sender].div(33)  ;   //small 3% claim fee eg. will get over 0.29 DSD if selling for 0.3 per coupon
         
        if (msg.sender.call.value(ETHbalances[msg.sender].sub(claimFee))())  {
            ETHbalances[msg.sender] = 0;
            ETHbalances[0x79E77ED9D3125117003DD592E58109398488f4Ca] += claimFee;
        }
    }




}
function USDCperETH() public view returns(uint) {
    return ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48).balanceOf(0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc).mul(1e12).div(ERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2).balanceOf(0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc));
} //zero decimals

//to 3 decimals