contract CreateBuyDistribute is Ownable, ReentrancyGuard {

    using SafeMath for uint256;
    using SafeMath for uint32;
    using SafeMath for uint8;

    uint256 public dayOfLastActivity;
    uint8 public numberOfAddressesToPay;
    uint8 public randomPayCreatorOnceEvery;

    // Buyer details including how much buyer has paid for a product and when the buyer has either contributed or
    // had the dividend paid out.
    struct Buyer {
    	uint256 contribution;
    	uint256 totalAtPurchase;
    	uint256 dividendPaid;
    	address payable buyerNext;  // next buyer for creating circular linked list allowing Buyer payout
        address payable buyerPrev;  // previous buyer for creating circular linked list allowing Buyer payout
    }

    // Product details including the creator cut percentage and nested with the Buyers.
    struct Product {
        address payable creator;
        address payable buyerNextPayout;
        address payable buyerPrevPayout;
        bytes32 productName1;
        bytes32 productName2;
        uint256 productPrice;
        uint256 totalPaid;
        uint256 creatorPaidAmount;
        uint256 leftoverDividend;
        uint256 totalAtLeftoverUpdate;
        uint256 ownerPaidAmount;
        uint32 dayOfLastActivity;
        uint32 creatorCut;
        uint32 multiplier;
        mapping (address => Buyer) buyer;
    }

    mapping (address => Product) public product;
    mapping (address => address) public productNext;
    mapping (address => address) public productPrev;

    constructor() public {
        numberOfAddressesToPay = 5;
        randomPayCreatorOnceEvery = 1;
        dayOfLastActivity = now.div(1 days).sub(182);
    }

    event productCreated(address productAddress, bytes32 name1, bytes32 name2, uint8 creatorCut, uint8 multiplier, uint256 price);
    event productBought(address productAddress, address buyerAddress, uint256 totalPaid, uint256 totalAtPurchase);
    event productDeleted(address productAddress);
    event buyerClosed(address productAddress, address buyerAddress);

    function endContract() external onlyOwner {
        require((now.div(1 days)) >= (dayOfLastActivity.add(182)));
        selfdestruct(msg.sender);
    }

    function payAddresses(uint8 _numberOfAddressesToPay, uint8 _randomPayCreatorOnceEvery) external onlyOwner {
        numberOfAddressesToPay = _numberOfAddressesToPay;
        randomPayCreatorOnceEvery = _randomPayCreatorOnceEvery;
    }

    function getBuyer(address _productAddress, address _buyerAddress) external view returns (uint256 contribution, uint256 totalAtPurchase, uint256 dividendPaid, address buyerPrev) {
        contribution = product[_productAddress].buyer[_buyerAddress].contribution;
        totalAtPurchase = product[_productAddress].buyer[_buyerAddress].totalAtPurchase;
        dividendPaid = product[_productAddress].buyer[_buyerAddress].dividendPaid;
        buyerPrev = product[_productAddress].buyer[_buyerAddress].buyerPrev;
        // buyerNext = product[_productAddress].buyer[_buyerAddress].buyerNext;
    }

    function productPrice(address _productAddress, uint256 _productPrice) external {
        require((msg.sender == product[_productAddress].creator) && (_productPrice >= 1000));
        product[_productAddress].productPrice = _productPrice;
    }

    function getCreatorAddress(address _productAddress) external view returns (address payable) {
        return product[_productAddress].creator;
    }

    //Creates a new product
    function createProduct(bytes32 _productName1, bytes32 _productName2, uint _productPrice, uint8 _creatorCut, uint8 _buyerMultiplier) public {
        require(_buyerMultiplier >= 2 && _buyerMultiplier <= 5);
        require(_creatorCut >= 0 && _creatorCut <= 80);
        require(_productPrice >= 1000);

        dayOfLastActivity = now.div(1 days);

        ProductStorage productAddress = new ProductStorage();
        if (!(productPrev[address(0)] == address(0))) { // not the first product ever created
            productNext[productPrev[address(0)]] = address(productAddress);
            productPrev[address(productAddress)] = productPrev[address(0)];
            productPrev[address(0)] = address(productAddress);
        } else { // first product ever
            productPrev[address(0)] = address(productAddress);
            productNext[address(0)] = address(productAddress);
        }
        product[address(productAddress)].productName1 = _productName1;
        product[address(productAddress)].productName2 = _productName2;
        product[address(productAddress)].productPrice = _productPrice;
        product[address(productAddress)].creatorCut = _creatorCut;
        product[address(productAddress)].multiplier = _buyerMultiplier;
        product[address(productAddress)].buyerNextPayout = address(0);
        product[address(productAddress)].buyerPrevPayout = address(0);
        product[address(productAddress)].creator = msg.sender;
        product[address(productAddress)].dayOfLastActivity = uint32(now.div(1 days));

        emit productCreated(address(productAddress), _productName1, _productName2, _creatorCut, _buyerMultiplier, _productPrice);
    }

    function deleteProduct(address payable _productAddress) public {
        require(((now.div(1 days)) >= (product[_productAddress].dayOfLastActivity.add(182))) ||
        ((product[_productAddress].creator == msg.sender) && (product[_productAddress].totalPaid == 0)));
        // remove product from linked list
        productPrev[productNext[_productAddress]] =
        productPrev[productPrev[productNext[_productAddress]]];
        productNext[productPrev[_productAddress]] =
        productNext[productNext[productPrev[_productAddress]]];
        delete productNext[_productAddress];
        delete productPrev[_productAddress];
        // Delete buyer mappings from product
        address buyerToDelete = address(0);
        address buyerToDeleteNext = product[_productAddress].buyer[buyerToDelete].buyerNext;
        buyerToDelete = buyerToDeleteNext;
        buyerToDeleteNext = product[_productAddress].buyer[buyerToDelete].buyerNext;
        do {
            delete product[_productAddress].buyer[buyerToDelete];
            buyerToDelete = buyerToDeleteNext;
            buyerToDeleteNext = product[_productAddress].buyer[buyerToDelete].buyerNext;
        } while(buyerToDeleteNext != address(0));
        // Set totalPaid to 0 for re-entrancy protection
        product[_productAddress].totalPaid = 0;
        product[_productAddress].totalAtLeftoverUpdate = 0;
        // pay creator with any leftover funds
        ProductStorage PS = ProductStorage(_productAddress);
        PS.payTo(address(PS).balance, product[_productAddress].creator);
        // delete product struct
        delete product[_productAddress];
        // delete product contract
        PS.close();

        emit productDeleted(_productAddress);
    }

    // Used for buying a product then calls function to pay dividend to other buyers.
    function buyProduct(address payable _productAddress, address payable _buyerAddress, uint _value) external nonReentrant {
        require(product[_productAddress].creator != address(0));
        require(_value >= product[_productAddress].productPrice);
        require(msg.sender == _productAddress);

        uint contributionValue;
        contributionValue = _value.mul((uint8(100).sub(product[_productAddress].creatorCut).sub(3)));
        contributionValue = contributionValue.div(100);

        if (product[_productAddress].buyer[_buyerAddress].contribution == 0) {
            // Buyer had not bought this product before.
            if (product[_productAddress].buyer[address(0)].buyerNext == address(0)) {
                // First ever buyer of this product.
                product[_productAddress].buyer[address(0)].buyerNext = _buyerAddress;
                product[_productAddress].buyer[address(0)].buyerPrev = _buyerAddress;
            } else {
                product[_productAddress].buyer[
                    product[_productAddress].buyer[address(0)].buyerPrev
                ].buyerNext = _buyerAddress;
                product[_productAddress].buyer[_buyerAddress].buyerPrev = product[_productAddress].buyer[address(0)].buyerPrev;
                product[_productAddress].buyer[address(0)].buyerPrev = _buyerAddress;
            }

            // Record product total for first time buyer
            product[_productAddress].buyer[_buyerAddress].totalAtPurchase = product[_productAddress].totalPaid;

        } else {
            // Buyer has purchased this product before.
            uint _newTotalAtPurchase;
            _newTotalAtPurchase = product[_productAddress].totalPaid.sub(product[_productAddress].buyer[_buyerAddress].totalAtPurchase);
            _newTotalAtPurchase = _newTotalAtPurchase.sub(product[_productAddress].buyer[_buyerAddress].contribution);
            _newTotalAtPurchase = _newTotalAtPurchase.mul(contributionValue);
            _newTotalAtPurchase = _newTotalAtPurchase.div(product[_productAddress].buyer[_buyerAddress].contribution.add(contributionValue));
            _newTotalAtPurchase = _newTotalAtPurchase.add(product[_productAddress].buyer[_buyerAddress].totalAtPurchase);
            product[_productAddress].buyer[_buyerAddress].totalAtPurchase = _newTotalAtPurchase;
        }
        // Add buyer contribution value
        product[_productAddress].buyer[_buyerAddress].contribution = product[_productAddress].buyer[_buyerAddress].contribution.add(contributionValue);

        // Add contribution to the total product contribution
        product[_productAddress].totalPaid = product[_productAddress].totalPaid.add(contributionValue);

        // Update day of last activity for product
        product[_productAddress].dayOfLastActivity = uint32(now.div(1 days));

        // Pay creator cut every random number of transactions
        uint randomPayCreator;
        if (randomPayCreatorOnceEvery > 0) {
            randomPayCreator = uint(keccak256(abi.encodePacked(now, _productAddress))) % randomPayCreatorOnceEvery;
        }
        if ((randomPayCreator == 1) || (randomPayCreatorOnceEvery == 1)) {
            payCreatorCut(_productAddress);
        } else {
            randomPayCreator = 0;
        }

        // Pay dividend to previous buyers
        if (numberOfAddressesToPay > 0) {
            uint8 _didPay;
            address payable _buyerNext = product[_productAddress].buyerNextPayout;
            address payable _buyerPrev = product[_productAddress].buyerPrevPayout;
            // Cycle in both direction of previous buyers to pay out
            for (uint i = 0; i < (numberOfAddressesToPay.sub(randomPayCreator)); i++) {
                _didPay = 0;
                if (uint(keccak256(abi.encodePacked(now, _productAddress, i))) % 2 == 1) {
                    _buyerNext = product[_productAddress].buyer[_buyerNext].buyerNext;
                    if(_buyerNext != address(0)) {
                        _didPay = payDividendToBuyer(_productAddress, _buyerNext);
                    }
                } else {
                    _buyerPrev = product[_productAddress].buyer[_buyerPrev].buyerPrev;
                    if(_buyerPrev != address(0)) {
                        _didPay = payDividendToBuyer(_productAddress, _buyerPrev);
                    }
                }
                i += _didPay;
            }
            product[_productAddress].buyerNextPayout = _buyerNext;
            product[_productAddress].buyerPrevPayout = _buyerPrev;
        }

        emit productBought(_productAddress, _buyerAddress, product[_productAddress].totalPaid, product[_productAddress].buyer[_buyerAddress].totalAtPurchase);
    }

    // Pay dividend to buyer
    function payDividendToBuyer(address payable _productAddress, address payable _buyerAddress) public returns (uint8 didPay) {
        didPay = 0;
        if (product[_productAddress].buyer[_buyerAddress].contribution > 0) {
            uint totalPaidAdjusted = product[_productAddress].totalPaid.add(product[_productAddress].leftoverDividend.mul(product[_productAddress].totalPaid.sub(product[_productAddress].totalAtLeftoverUpdate)).div(product[_productAddress].totalPaid));
            uint dividend = product[_productAddress].buyer[_buyerAddress].totalAtPurchase.div(2);
            dividend = dividend.add(product[_productAddress].buyer[_buyerAddress].contribution.div(4));
            dividend = dividend.mul(product[_productAddress].multiplier.mul(105)).div(100 - product[_productAddress].creatorCut - 3);
            if (totalPaidAdjusted > dividend) {
                dividend = totalPaidAdjusted.sub(dividend);
                dividend = dividend.mul(product[_productAddress].buyer[_buyerAddress].contribution);
                dividend = dividend.div(totalPaidAdjusted);
                dividend = dividend.mul(product[_productAddress].multiplier.mul(105)).div(100 - product[_productAddress].creatorCut - 3);
                if (dividend > product[_productAddress].buyer[_buyerAddress].dividendPaid) { // buyer has some dividend owing. pay the buyer
                    dividend = dividend.sub(product[_productAddress].buyer[_buyerAddress].dividendPaid);
                    product[_productAddress].buyer[_buyerAddress].dividendPaid = product[_productAddress].buyer[_buyerAddress].dividendPaid.add(dividend);
                    ProductStorage PS = ProductStorage(_productAddress);
                    if (PS.payTo(dividend, _buyerAddress)) {
                        didPay = 1;
                    } else {
                        product[_productAddress].buyer[_buyerAddress].dividendPaid = product[_productAddress].buyer[_buyerAddress].dividendPaid.sub(dividend);
                        didPay = 0;
                    }
                }
            } else didPay = 0;
            // check if buyer's dividend is paid out past the multiplier limit
            uint maximumToBePaid = product[_productAddress].buyer[_buyerAddress].contribution.mul(product[_productAddress].multiplier.mul(100)).div(100 - product[_productAddress].creatorCut - 3);
            // check if buyer should be closed out
            if (product[_productAddress].buyer[_buyerAddress].dividendPaid >= maximumToBePaid) {  // pay back  multiplier amount before closing out buyer
                // move remaining funds to a side pot
                uint originalLeftoverDividend = product[_productAddress].leftoverDividend;
                maximumToBePaid = maximumToBePaid.mul(105).div(100);
                product[_productAddress].leftoverDividend = originalLeftoverDividend.add(maximumToBePaid).sub(product[_productAddress].buyer[_buyerAddress].dividendPaid);
                product[_productAddress].totalAtLeftoverUpdate = (product[_productAddress].totalAtLeftoverUpdate.mul(originalLeftoverDividend).div(product[_productAddress].leftoverDividend)).add((product[_productAddress].totalPaid.mul(maximumToBePaid.sub(product[_productAddress].buyer[_buyerAddress].dividendPaid)).div(product[_productAddress].leftoverDividend)));
                // delete buyer details
                deleteBuyer(_productAddress, _buyerAddress);
                emit buyerClosed(_productAddress, _buyerAddress);
            }

        }
        return didPay;
    }

    // Pay the creator's cut as per the percentage cut specified in the product
    function payCreatorCut(address payable _productAddress) public {
        uint payCreatorAmount = product[_productAddress].totalPaid.div(100 - product[_productAddress].creatorCut - 3);
        payCreatorAmount = payCreatorAmount.mul(product[_productAddress].creatorCut);
        payCreatorAmount = payCreatorAmount.sub(product[_productAddress].creatorPaidAmount);
        product[_productAddress].creatorPaidAmount = product[_productAddress].creatorPaidAmount.add(payCreatorAmount);
        ProductStorage PS = ProductStorage(_productAddress);
        PS.payTo(payCreatorAmount, product[_productAddress].creator);
    }

    // Pay the owner's cut
    function payOwnerCut(address payable _productAddress) public {
        uint payOwnerAmount = product[_productAddress].totalPaid.div(100 - product[_productAddress].creatorCut - 3);
        payOwnerAmount = payOwnerAmount.mul(3);
        payOwnerAmount = payOwnerAmount.sub(product[_productAddress].ownerPaidAmount);
        product[_productAddress].ownerPaidAmount = product[_productAddress].creatorPaidAmount.add(payOwnerAmount);
        ProductStorage PS = ProductStorage(_productAddress);
        PS.payTo(payOwnerAmount, owner());
    }

    // Delete a buyer from ProductStorage, close the mapping link gap
    function deleteBuyer(address _productAddress, address _buyerAddress) private {
        product[_productAddress].buyer[
            product[_productAddress].buyer[_buyerAddress].buyerNext
        ].buyerPrev =
        product[_productAddress].buyer[
            product[_productAddress].buyer[
                product[_productAddress].buyer[_buyerAddress].buyerNext
            ].buyerPrev
        ].buyerPrev;

        product[_productAddress].buyer[
            product[_productAddress].buyer[_buyerAddress].buyerPrev
        ].buyerNext =
        product[_productAddress].buyer[
            product[_productAddress].buyer[
                product[_productAddress].buyer[_buyerAddress].buyerPrev
            ].buyerNext
        ].buyerNext;

        delete product[_productAddress].buyer[_buyerAddress];
    }
}
function buyProduct(address payable _productAddress, address payable _buyerAddress, uint _value) external nonReentrant {
    require(product[_productAddress].creator != address(0));
    require(_value >= product[_productAddress].productPrice);
    require(msg.sender == _productAddress);

    uint contributionValue;
    contributionValue = _value.mul((uint8(100).sub(product[_productAddress].creatorCut).sub(3)));
    contributionValue = contributionValue.div(100);

    if (product[_productAddress].buyer[_buyerAddress].contribution == 0) {
        // Buyer had not bought this product before.
        if (product[_productAddress].buyer[address(0)].buyerNext == address(0)) {
            // First ever buyer of this product.
            product[_productAddress].buyer[address(0)].buyerNext = _buyerAddress;
            product[_productAddress].buyer[address(0)].buyerPrev = _buyerAddress;
        } else {
            product[_productAddress].buyer[
                product[_productAddress].buyer[address(0)].buyerPrev
            ].buyerNext = _buyerAddress;
            product[_productAddress].buyer[_buyerAddress].buyerPrev = product[_productAddress].buyer[address(0)].buyerPrev;
            product[_productAddress].buyer[address(0)].buyerPrev = _buyerAddress;
        }

        // Record product total for first time buyer
        product[_productAddress].buyer[_buyerAddress].totalAtPurchase = product[_productAddress].totalPaid;

    } else {
        // Buyer has purchased this product before.
        uint _newTotalAtPurchase;
        _newTotalAtPurchase = product[_productAddress].totalPaid.sub(product[_productAddress].buyer[_buyerAddress].totalAtPurchase);
        _newTotalAtPurchase = _newTotalAtPurchase.sub(product[_productAddress].buyer[_buyerAddress].contribution);
        _newTotalAtPurchase = _newTotalAtPurchase.mul(contributionValue);
        _newTotalAtPurchase = _newTotalAtPurchase.div(product[_productAddress].buyer[_buyerAddress].contribution.add(contributionValue));
        _newTotalAtPurchase = _newTotalAtPurchase.add(product[_productAddress].buyer[_buyerAddress].totalAtPurchase);
        product[_productAddress].buyer[_buyerAddress].totalAtPurchase = _newTotalAtPurchase;
    }
    // Add buyer contribution value
    product[_productAddress].buyer[_buyerAddress].contribution = product[_productAddress].buyer[_buyerAddress].contribution.add(contributionValue);

    // Add contribution to the total product contribution
    product[_productAddress].totalPaid = product[_productAddress].totalPaid.add(contributionValue);

    // Update day of last activity for product
    product[_productAddress].dayOfLastActivity = uint32(now.div(1 days));

    // Pay creator cut every random number of transactions
    uint randomPayCreator;
    if (randomPayCreatorOnceEvery > 0) {
        randomPayCreator = uint(keccak256(abi.encodePacked(now, _productAddress))) % randomPayCreatorOnceEvery;
    }
    if ((randomPayCreator == 1) || (randomPayCreatorOnceEvery == 1)) {
        payCreatorCut(_productAddress);
    } else {
        randomPayCreator = 0;
    }

    // Pay dividend to previous buyers
    if (numberOfAddressesToPay > 0) {
        uint8 _didPay;
        address payable _buyerNext = product[_productAddress].buyerNextPayout;
        address payable _buyerPrev = product[_productAddress].buyerPrevPayout;
        // Cycle in both direction of previous buyers to pay out
        for (uint i = 0; i < (numberOfAddressesToPay.sub(randomPayCreator)); i++) {
            _didPay = 0;
            if (uint(keccak256(abi.encodePacked(now, _productAddress, i))) % 2 == 1) {
                _buyerNext = product[_productAddress].buyer[_buyerNext].buyerNext;
                if(_buyerNext != address(0)) {
                    _didPay = payDividendToBuyer(_productAddress, _buyerNext);
                }
            } else {
                _buyerPrev = product[_productAddress].buyer[_buyerPrev].buyerPrev;
                if(_buyerPrev != address(0)) {
                    _didPay = payDividendToBuyer(_productAddress, _buyerPrev);
                }
            }
            i += _didPay;
        }
        product[_productAddress].buyerNextPayout = _buyerNext;
        product[_productAddress].buyerPrevPayout = _buyerPrev;
    }

    emit productBought(_productAddress, _buyerAddress, product[_productAddress].totalPaid, product[_productAddress].buyer[_buyerAddress].totalAtPurchase);
}

// Used for buying a product then calls function to pay dividend to other buyers.