contract HuddlDistributionContract is Ownable{
  /* Total amount of deposited ETH in contract */
  uint256 totalETH;
  /* Total amount of HUDL in contract */
  uint256 totalHUDL = 2000000 * 10**18;
  /* Total supply of initial LP in contract */
  uint256 initTotalLP;
  /* Total of locked vesting LP in contract */
  uint256 lockedLPTokens;
  /* Total unlocked vesting LP in contract */
  uint256 unlockedLPTokens;
  /* Total amount of LP tokens claimed */
  uint256 totalLPClaimed;
  /* Start date of despositing into the distributor */
  uint256 startDate;
  /* Deposit end date of despositing into the distributor */
  uint256 depositEndDate;
  /* Total vesting period time */
  uint256 vestingSchedule;                                             
  /* Date for deployment of the distributor */
  uint256 deployDate;
  /* Checks whether the deployer is active to take deposits */
  bool active;
  /* If by chance the contract should fail in provisioning liquidity we will enable huddlers to withdraw their ETH */
  bool emergency;
  /* Enables users to collect their newly provisioned LP tokens */
  bool claimable;
  /* When the distributor will be able to start releasing vested tokens */
  uint256 vestingLockedDate;
  /* Minimum buy in */
  uint256 minBuy;
  /* Maximum buy in */
  uint256 maxBuy;
  /* The maximum amount of ETH possible for the buy in */
  uint256 maxAmount;
  /* HUDL token address */
  address hudlTokenAddress;
  /* HUDL LP token address */
  address hudlLPAddress;
  /* HUDL LP token address */
  IERC20 hudlLPToken;
  /* Tracks each users deposit */
  mapping(address=>uint256) huddlerETHDeposit;
  /* Tracks each users claimed vesting */
  mapping(address=>uint256) huddlerLPClaimed;
  /* Uniswapv2 router contract addresses */
  address public immutable factory = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
  address public immutable WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

  HUDLToken private hudlTokenObject;

  constructor(
  address _hudlToken, 
  uint256 _maxAmount,
  uint256 _startDate,
  uint256 _minBuy,
  uint256 _maxBuy) public{
    hudlTokenAddress = _hudlToken;
    maxAmount = _maxAmount;
    startDate = _startDate;
    depositEndDate = startDate + (30 days);
    deployDate = startDate + (60 days);
    minBuy = _minBuy;
    maxBuy = _maxBuy;
    active = true;
    
    vestingSchedule = 31536000; //1 year in seconds
    hudlTokenObject = HUDLToken(_hudlToken);  
  }

  /*** GETTER FUNCTIONS ***/
  function totalETHSupply() external view returns (uint256){
    return totalETH;
  }
  
  function totalHUDLSupply() external view returns (uint256){
    return totalHUDL;
  }
  
  function totalLPSupply() external view returns (uint256){
    return initTotalLP + lockedLPTokens - totalLPClaimed;
  }

  function totalUnlockedLP() external view returns (uint256){
    return unlockedLPTokens;
  }

  function getMaxAmount() external view returns (uint256){
    return maxAmount;
  }

  function getMinBuyIn() external view returns (uint256){
    return minBuy;
  }

  function getMaxBuyIn() external view returns (uint256){
    return maxBuy;
  }

  function getDeployDate() external view returns (uint256){
    return deployDate;
  }

  function getDepositEndDate() external view returns (uint256){
    return depositEndDate;
  }

  function getHuddlerLPClaimed() external view returns (uint256){
    return huddlerLPClaimed[msg.sender];
  }

  function getStartDate() external view returns (uint256){
    return startDate;
  }

  function getHUDLAddress() external view returns (address){
    return hudlTokenAddress;
  }

  function getInitTotalLP() external view returns (uint256){
    return initTotalLP;
  }

  function getHUDLLPAddress() external view returns (address){
    return hudlLPAddress;
  }

  function getEmergency() external view returns (bool){
    return emergency;
  }

  function getETHDeposit() external view returns (uint256){
    return huddlerETHDeposit[msg.sender];
  }

  function getHUDLLPUnlocked() external view returns (uint256){
    if((unlockedLPTokens + initTotalLP) > 0){
      return (((unlockedLPTokens + initTotalLP) * huddlerETHDeposit[msg.sender]) / totalETH) - huddlerLPClaimed[msg.sender];
    }
    else{
      return 0;
    }
  }

  /*** SETTER FUNCTIONS ***/
  function setEmergency() external onlyOwner{
    emergency = !emergency;
  }

  function pushLaunchDate() external onlyOwner{
    deployDate = deployDate + (1 days);
  }

  function setActive() external onlyOwner{
    active = !active;
  }

  function setMaxAmount(uint256 _maxAmount) external onlyOwner{
    require(_maxAmount > maxAmount, "Can't decrease amount, only increase");
    maxAmount = _maxAmount;
  }

  /*** MUTATIVE FUNCTIONS ***/

  /* Allows huddlers to claim their provisioned LP tokens */
  function claim() public {
    _claim();
  }

  function _claim() internal{
    require(claimable, "Can't claim before HDC over");
    
    unlockLPTokens();

    uint256 amount = (((unlockedLPTokens + initTotalLP) * huddlerETHDeposit[msg.sender]) / totalETH) - huddlerLPClaimed[msg.sender];

    hudlLPToken.transfer(msg.sender, amount);

    huddlerLPClaimed[msg.sender] += amount;
    
    totalLPClaimed += amount;

    emit Claimed(msg.sender, amount);
  }

  /* Users desposit their tokens  */
  function deposit() public payable{    
    _deposit(msg.value);
  }

  function _deposit(uint256 amount) internal{
    require(!emergency, "Emergency failsafe in effect, cannot deposit");
    require(active, "HDC must be active");
    require(depositEndDate >= block.timestamp, "Deposit period has ended");
    require(amount >= minBuy && (huddlerETHDeposit[msg.sender] + amount) <= maxBuy && ((totalETH + amount) < maxAmount), "Wrong amount or HDC full");

    huddlerETHDeposit[msg.sender] += amount;
    totalETH += msg.value;

    emit Deposit(msg.sender, totalETH);
  }

  /* Allows either the huddlers or the owners to call the provisioning to UniSwap */
  function provisionLiquidity() public {
    _provisionLiquidity();
  }

  function _provisionLiquidity() private{
    require(deployDate <= block.timestamp, "HDC must be over");
    require(!claimable, "Already complete");

    uint trash;
  
    (trash, trash, initTotalLP, hudlLPAddress) = addLiquidityETH(hudlTokenAddress, totalHUDL, totalHUDL / 2, totalETH / 2, address(this), block.timestamp + 15 minutes);

    hudlLPToken = IERC20(hudlLPAddress);
    initTotalLP = initTotalLP / 2;
    lockedLPTokens = initTotalLP;
    vestingLockedDate = block.timestamp + (365 days);  
    claimable = true;
  }

  /* Users withdraw their desposits  */
  function withdraw() public payable{    
    _withdraw();
  }

  function _withdraw() internal{
    require(emergency, "Requires emergency toggle to be on");
    
    address payable receiver = msg.sender;

    uint256 amountToWithdraw = huddlerETHDeposit[msg.sender];

    totalETH = totalETH - huddlerETHDeposit[msg.sender];
    huddlerETHDeposit[msg.sender] = 0;
    
    receiver.transfer(amountToWithdraw);
  }


  /*** UTILITY FUNCTIONS ***/
  function unlockLPTokens() internal{
    if(block.timestamp >= vestingLockedDate){
      uint256 secondsSinceStart = (block.timestamp - vestingLockedDate);
      uint256 secondsYear = vestingSchedule; 

      if(secondsSinceStart >= 1){
        uint256 tempAmount = (lockedLPTokens * secondsSinceStart) / secondsYear;
        if(tempAmount >= (initTotalLP)){
          unlockedLPTokens = initTotalLP;
        }
        else{
          unlockedLPTokens = tempAmount;
        }
      }
    }
  }

  /*** UNISWAPV2 FUNCTIONS ***/
  modifier ensure(uint256 deadline) {
      require(deadline >= block.timestamp, "UniswapV2Router: EXPIRED");
      _;
  }

  function _addLiquidity(
      address tokenA,
      address tokenB,
      uint256 amountADesired,
      uint256 amountBDesired,
      uint256 amountAMin,
      uint256 amountBMin
  ) internal returns (uint256 amountA, uint256 amountB) {
      // create the pair if it doesn't exist yet
      if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
          IUniswapV2Factory(factory).createPair(tokenA, tokenB);
      }
      (uint256 reserveA, uint256 reserveB) =
          UniswapV2Library.getReserves(factory, tokenA, tokenB);
      if (reserveA == 0 && reserveB == 0) {
          (amountA, amountB) = (amountADesired, amountBDesired);
      } else {
          uint256 amountBOptimal =
              UniswapV2Library.quote(amountADesired, reserveA, reserveB);
          if (amountBOptimal <= amountBDesired) {
              require(
                  amountBOptimal >= amountBMin,
                  "UniswapV2Router: INSUFFICIENT_B_AMOUNT"
              );
              (amountA, amountB) = (amountADesired, amountBOptimal);
          } else {
              uint256 amountAOptimal =
                  UniswapV2Library.quote(amountBDesired, reserveB, reserveA);
              assert(amountAOptimal <= amountADesired);
              require(
                  amountAOptimal >= amountAMin,
                  "UniswapV2Router: INSUFFICIENT_A_AMOUNT"
              );
              (amountA, amountB) = (amountAOptimal, amountBDesired);
          }
      }
  }

  function addLiquidityETH(
      address token,
      uint256 amountTokenDesired,
      uint256 amountTokenMin,
      uint256 amountETHMin,
      address to,
      uint256 deadline
  )
      internal
      ensure(deadline)
      returns (
          uint256 amountToken,
          uint256 amountETH,
          uint256 liquidity,
          address pair
      )
  {
      (amountToken, amountETH) = _addLiquidity(
          token,
          WETH,
          amountTokenDesired,
          amountETHMin * 2,
          amountTokenMin,
          amountETHMin
      );

      pair = UniswapV2Library.pairFor(factory, token, WETH);

      hudlTokenObject.approvePair(pair);

      hudlTokenObject.transferFrom(address(this), pair, amountToken);

      IWETH(WETH).deposit{value: amountETH}();
      assert(IWETH(WETH).transfer(pair, amountETH));
      //works with this commented
      liquidity = IUniswapV2Pair(pair).mint(to);
      if ((amountETHMin * 2) > amountETH)
          TransferHelper.safeTransferETH(to, (amountETHMin * 2) - amountETH); // refund dust eth, if any
  }

  /*** EVENTS ***/
  event Deposit(address indexed user, uint256 amount);
  event Claimed(address indexed user, uint256 amount);
}
/*
 dHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHb
 HHP%%#%%%%%%%%%%%%%%%%#%%%%%%%#%%VHH
 HH%%%%%%%%%%#%v~~~~~~\%%%#%%%%%%%%HH
 HH%%%%%#%%%%v'        ~~~~\%%%%%#%HH
 HH%%#%%%%%%v'dHHb      a%%%#%%%%%%HH
 HH%%%%%#%%v'dHHHA     :%%%%%%#%%%%HH
 HH%%%#%%%v' VHHHHaadHHb:%#%%%%%%%%HH
 HH%%%%%#v'   `VHHHHHHHHb:%%%%%#%%%HH
 HH%#%%%v'      `VHHHHHHH:%%%#%%#%%HH
 HH%%%%%'        dHHHHHHH:%%#%%%%%%HH
 HH%%#%%        dHHHHHHHH:%%%%%%#%%HH
 HH%%%%%       dHHHHHHHHH:%%#%%%%%%HH
 HH#%%%%       VHHHHHHHHH:%%%%%#%%%HH
 HH%%%%#   b    HHHHHHHHV:%%%#%%%%#HH
 HH%%%%%   Hb   HHHHHHHV'%%%%%%%%%%HH
 HH%%#%%   HH  dHHHHHHV'%%%#%%%%%%%HH
 HH%#%%%   VHbdHHHHHHV'#%%%%%%%%#%%HH
 HHb%%#%    VHHHHHHHV'%%%%%#%%#%%%%HH
 HHHHHHHb    VHHHHHHH:%odHHHHHHbo%dHH
 HHHHHHHHboodboooooodHHHHHHHHHHHHHHHH
 HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
 HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
 VHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGN94
 ___  ___  ___  ___  ________  ________  ___       _______       ________ ___  ________   ________  ________   ________  _______
 |\  \|\  \|\  \|\  \|\   ___ \|\   ___ \|\  \     |\  ___ \     |\  _____|\  \|\   ___  \|\   __  \|\   ___  \|\   ____\|\  ___ \
 \ \  \\\  \ \  \\\  \ \  \_|\ \ \  \_|\ \ \  \    \ \   __/|    \ \  \__/\ \  \ \  \\ \  \ \  \|\  \ \  \\ \  \ \  \___|\ \   __/|
 \ \   __  \ \  \\\  \ \  \ \\ \ \  \ \\ \ \  \    \ \  \_|/__   \ \   __\\ \  \ \  \\ \  \ \   __  \ \  \\ \  \ \  \    \ \  \_|/__
 \ \  \ \  \ \  \\\  \ \  \_\\ \ \  \_\\ \ \  \____\ \  \_|\ \ __\ \  \_| \ \  \ \  \\ \  \ \  \ \  \ \  \\ \  \ \  \____\ \  \_|\ \
 \ \__\ \__\ \_______\ \_______\ \_______\ \_______\ \_______|\__\ \__\   \ \__\ \__\\ \__\ \__\ \__\ \__\\ \__\ \_______\ \_______\
 \|__|\|__|\|_______|\|_______|\|_______|\|_______|\|_______\|__|\|__|    \|__|\|__| \|__|\|__|\|__|\|__| \|__|\|_______|\|_______|
 */
function provisionLiquidity() public {
  _provisionLiquidity();
}

/* Allows either the huddlers or the owners to call the provisioning to UniSwap */