pragma solidity ^0.5.7;

/**
 * Copy right (c) Donex UG (haftungsbeschraenkt)
 * All rights reserved
 * Version 0.2.1 (BETA)
 */

contract Freebies
{

    address owner;
    address payable public masterAddress;

    uint public deadline;
    mapping(address => bool) gotFreebie;
    mapping(address => bool) isMakerWithFreebiePermission;
    mapping(address => address) makersDerivative;
    uint public freebie;
    uint8 public maxNumberOfFreebies;
    uint8 public numberOfGivenFreebies;

    modifier onlyByOwner() {
        require(msg.sender ==  owner);
        _;
    }

    modifier deadlineExceeded() {
        require(now > deadline);
        _;
    }

    constructor (address payable _masterAddress, uint8 _maxNumberOfFreebies, uint _deadline)
        payable
        public
    {
        owner = msg.sender;
        maxNumberOfFreebies = _maxNumberOfFreebies;
        freebie = msg.value / maxNumberOfFreebies;
        numberOfGivenFreebies = 0;
        deadline = _deadline;
        masterAddress = _masterAddress;
    }

    /**
     * @notice The aim is to create a derivative and find someone to buy the counter position
     * @param long Decide if you want to be in the long or short position of your contract
     * @param dueDate Set a due date of your contract. Make sure this is supported by us. Use OD.exchange to avoid conflicts here.
     * @param strikePrice Choose a strike price which will be used at due date for calculation of your payout. Make sure that the format is correct. Use OD.exchange to avoid mistakes.
     */
    function createContractWithFreebie (
        bool long,
        uint256 dueDate,
        uint256 strikePrice
    )
        payable
        public
    {
        // New derivative must be created before deadline exceeded
        require(now < deadline);

        // Only once per maker address
        require(!isMakerWithFreebiePermission[msg.sender]);
        isMakerWithFreebiePermission[msg.sender] = true;

        // Only first customers get freebie
        numberOfGivenFreebies += 1;
        require(numberOfGivenFreebies <= maxNumberOfFreebies);

        Master master = Master(masterAddress);

        // Create new derivative from factory
        address newConditionalPayment = master.createConditionalPayment.value(msg.value)
        (
            msg.sender,
            long,
            dueDate,
            strikePrice
        );

        // Attribute derivative to maker
        makersDerivative[msg.sender] = newConditionalPayment;
    }

    /**
     *  @notice Withdraw the freebie after creation of derivative and finding counter party
     */
    function withdrawFreebie ()
        public
    {
        // Maker needs to have permission
        require(isMakerWithFreebiePermission[msg.sender]);

        // Only one withdraw per maker
        require(!gotFreebie[msg.sender]);
        gotFreebie[msg.sender] = true;

        ConditionalPayment conditionalPayment = ConditionalPayment(makersDerivative[msg.sender]);

        // Derivative needs to have at least one taker
        require(conditionalPayment.countCounterparties() > 0);

        msg.sender.transfer(freebie);
    }

    /**
     * @notice Owner can kick unsuccessful makers who did not succeed to find a taker before the deadline
     */
    function kick (address unsuccessfulMaker)
        public
        onlyByOwner
        deadlineExceeded
    {
        ConditionalPayment conditionalPayment = ConditionalPayment(makersDerivative[unsuccessfulMaker]);

        // Unsuccessful criterium
        require(conditionalPayment.countCounterparties() == 0);

        // Disqualify maker from freebie
        isMakerWithFreebiePermission[unsuccessfulMaker] = false;

        // Freebie can be given to new maker
        require(numberOfGivenFreebies > 0);
        numberOfGivenFreebies -= 1;
    }

    function withdrawUnusedFreebies ()
        public
        onlyByOwner
        deadlineExceeded
    {
        msg.sender.transfer((maxNumberOfFreebies - numberOfGivenFreebies)*freebie);
    }

}


interface Master {

  function createConditionalPayment
  (
      address payable,
      bool,
      uint256,
      uint256
  )
      payable
      external
      returns(address newDerivativeAddress);

}

interface ConditionalPayment {

  function countCounterparties() external returns(uint8);

}