pragma solidity ^0.4.24;

/*
* Team FreedomBird presents..
* ====================================*
*   ██████╗  ██████╗  ██████╗
*   ██╔══██╗██╔═══██╗██╔════╝
*   ██████╔╝██║   ██║██║
*   ██╔═══╝ ██║   ██║██║
*   ██║     ╚██████╔╝╚██████╗
*   ╚═╝      ╚═════╝  ╚═════╝
*   power of community.社区动力
* ====================================*
*
* -> What?
* PoC is the proof of 3G community, the base rules:
* 1. Buy or sell token will take 10% dividends fee to all poc token holders
* 2. Holding over 100 PoC Tokens can be master node, All players who enter the contract through your Masternode have 33% of their 10% dividends fee rerouted from the master-node
* More stable than ever, having withstood severe testnet abuse and attack attempts from our community!.
* Visit https://poc3g.com for more information.
* Join PoC, build the community, earning money and get freedom!
*/

contract PowerOfCommunity {
  /*=================================
  =            MODIFIERS            =
  =================================*/
  // only people with tokens.
  modifier onlyBagholders() {
    require(myTokens() > 0);
    _;
  }

  // only people with profits.
  modifier onlyStronghands() {
    require(myDividends(true) > 0);
    _;
  }

  // administrators can:
  // -> change the name of the contract
  // -> change the name of the token
  // -> change the PoS difficulty (How many tokens it costs to hold a masternode, in case it gets crazy high later)
  // they CANNOT:
  // -> take funds
  // -> disable withdrawals
  // -> kill the contract
  // -> change the price of tokens
  modifier onlyAdministrator(){
    address _customerAddress = msg.sender;
    require(administrators[keccak256(abi.encodePacked(_customerAddress))], 'only administrator can do it');
    _;
  }


  // ensures that the first tokens in the contract will be equally distributed.
  // meaning, no divine dump will be ever possible.
  // result: healthy longevity.
  modifier antiEarlyWhale(uint256 _amountOfEthereum){
    address _customerAddress = msg.sender;

    // are we still in the vulnerable phase?
    // if so, enact anti early whale protocol
    if( onlyAmbassadors && ((totalEthereumBalance() - _amountOfEthereum) <= ambassadorQuota_ )){
      require(
      // is the customer in the ambassador list?
        ambassadors_[_customerAddress] == true &&

        // does the customer purchase exceed the max ambassador quota?
        (ambassadorAccumulatedQuota_[_customerAddress] + _amountOfEthereum) <= ambassadorMaxPurchase_

      );

      // updated the accumulated quota
      ambassadorAccumulatedQuota_[_customerAddress] = SafeMath.add(ambassadorAccumulatedQuota_[_customerAddress], _amountOfEthereum);

      // execute
      _;
    } else {
      // in case the ether count drops low, the ambassador phase won't reinitiate.
      onlyAmbassadors = false;
      _;
    }

  }


  /*==============================
  =            EVENTS            =
  ==============================*/
  event onTokenPurchase(
    address indexed customerAddress,
    uint256 incomingEthereum,
    uint256 tokensMinted,
    address indexed referredBy
  );

  event onTokenSell(
    address indexed customerAddress,
    uint256 tokensBurned,
    uint256 ethereumEarned
  );

  event onReinvestment(
    address indexed customerAddress,
    uint256 ethereumReinvested,
    uint256 tokensMinted
  );

  event onWithdraw(
    address indexed customerAddress,
    uint256 ethereumWithdrawn
  );

  // ERC20
  event Transfer(
    address indexed from,
    address indexed to,
    uint256 tokens
  );

  event onAirdrop (
    address indexed customerAddress,
    uint256 ethereumAirdrop
  );


  /*=====================================
  =            CONFIGURABLES            =
  =====================================*/
  string public name = "Power of Community";
  string public symbol = "PoC";
  uint8 constant public decimals = 18;
  uint8 constant internal dividendFee_ = 10;
  uint256 constant internal tokenPriceInitial_ = 0.0000001 ether;
  uint256 constant internal tokenPriceIncremental_ = 0.00000001 ether;
  uint256 constant internal magnitude = 2**64;

  // proof of stake (defaults at 100 tokens)
  uint256 public stakingRequirement = 100e18;

  // ambassador program
  mapping(address => bool) internal ambassadors_;
  uint256 constant internal ambassadorMaxPurchase_ = 1 ether;
  uint256 constant internal ambassadorQuota_ = 20 ether;



  /*================================
   =            DATASETS            =
   ================================*/
  // amount of shares for each address (scaled number)
  mapping(address => uint256) internal tokenBalanceLedger_;
  mapping(address => uint256) internal referralBalance_;
  mapping(address => int256) internal payoutsTo_;
  mapping(address => uint256) internal ambassadorAccumulatedQuota_;
  uint256 internal tokenSupply_ = 0;
  uint256 internal profitPerShare_;

  // administrator list (see above on what they can do)
  mapping(bytes32 => bool) public administrators;

  // when this is set to true, only ambassadors can purchase tokens (this prevents a whale premine, it ensures a fairly distributed upper pyramid)
  bool public onlyAmbassadors = true;



  /*=======================================
  =            PUBLIC FUNCTIONS            =
  =======================================*/
  /*
  * -- APPLICATION ENTRY POINTS --
  */
  constructor()
  public
  {
    // add administrators here
    administrators[0x5459dfc1e0ca91cd608719d708525d3feec9ff93fb124f99900ab703862bfbb5] = true;

    // add the ambassadors here.
    // contributors that need to remain private out of security concerns.
    ambassadors_[0xf490eA414651d6c43ED5233e82AC0AA7C73a2ff8] = true; // gl
    ambassadors_[0x84755198234ea33C19C7db8a470f30CB4B4c3204] = true; // fm
    ambassadors_[0x53569B4936D17dA60EDef18ce9c177291529F03E] = true; // rz
    ambassadors_[0x68aa9131666210a55C7Ea1BfA6bA3d8e906E1a5B] = true; // ds
    ambassadors_[0xc377bCA574536B678e94cc614f77D1f271eEa063] = true; // bw
    ambassadors_[0xb1D68138E6bD476b559710341584f7D79E8DEd27] = true; // st
    ambassadors_[0x4d607EB2c41AAEaf6942879afc11bEda30259Af6] = true; // cxc
  }


  /**
   * Converts all incoming ethereum to tokens for the caller, and passes down the referral addy (if any)
   */
  function buy(address _referredBy)
  public
  payable
  returns(uint256)
  {
    purchaseTokens(msg.value, _referredBy);
  }

  /**
   * Fallback function to handle ethereum that was send straight to the contract
   * Unfortunately we cannot use a referral address this way.
   */
  function()
  payable
  public
  {
    purchaseTokens(msg.value, 0x0);
  }

  /**
   * Converts all of caller's dividends to tokens.
   */
  function reinvest()
  onlyStronghands()
  public
  {
    // fetch dividends
    uint256 _dividends = myDividends(false); // retrieve ref. bonus later in the code

    // pay out the dividends virtually
    address _customerAddress = msg.sender;
    payoutsTo_[_customerAddress] +=  (int256) (_dividends * magnitude);

    // retrieve ref. bonus
    _dividends += referralBalance_[_customerAddress];
    referralBalance_[_customerAddress] = 0;

    // dispatch a buy order with the virtualized "withdrawn dividends"
    uint256 _tokens = purchaseTokens(_dividends, 0x0);

    // fire event
    emit onReinvestment(_customerAddress, _dividends, _tokens);
  }

  /**
   * Alias of sell() and withdraw().
   */
  function exit()
  public
  {
    // get token count for caller & sell them all
    address _customerAddress = msg.sender;
    uint256 _tokens = tokenBalanceLedger_[_customerAddress];
    if(_tokens > 0) sell(_tokens);

    // lambo delivery service
    withdraw();
  }

  /**
   * Withdraws all of the callers earnings.
   */
  function withdraw()
  onlyStronghands()
  public
  {
    // setup data
    address _customerAddress = msg.sender;
    uint256 _dividends = myDividends(false); // get ref. bonus later in the code

    // update dividend tracker
    payoutsTo_[_customerAddress] +=  (int256) (_dividends * magnitude);

    // add ref. bonus
    _dividends += referralBalance_[_customerAddress];
    referralBalance_[_customerAddress] = 0;

    // lambo delivery service
    _customerAddress.transfer(_dividends);

    // fire event
    emit onWithdraw(_customerAddress, _dividends);
  }

  /**
   * Liquifies tokens to ethereum.
   */
  function sell(uint256 _amountOfTokens)
  onlyBagholders()
  public
  {
    // setup data
    address _customerAddress = msg.sender;
    // russian hackers BTFO
    require(_amountOfTokens <= tokenBalanceLedger_[_customerAddress]);
    uint256 _tokens = _amountOfTokens;
    uint256 _ethereum = tokensToEthereum_(_tokens);
    uint256 _dividends = SafeMath.div(_ethereum, dividendFee_);
    uint256 _taxedEthereum = SafeMath.sub(_ethereum, _dividends);

    // burn the sold tokens
    tokenSupply_ = SafeMath.sub(tokenSupply_, _tokens);
    tokenBalanceLedger_[_customerAddress] = SafeMath.sub(tokenBalanceLedger_[_customerAddress], _tokens);

    // update dividends tracker
    int256 _updatedPayouts = (int256) (profitPerShare_ * _tokens + (_taxedEthereum * magnitude));
    payoutsTo_[_customerAddress] -= _updatedPayouts;

    // dividing by zero is a bad idea
    if (tokenSupply_ > 0) {
      // update the amount of dividends per token
      profitPerShare_ = SafeMath.add(profitPerShare_, (_dividends * magnitude) / tokenSupply_);
    }

    // fire event
    emit onTokenSell(_customerAddress, _tokens, _taxedEthereum);
  }


  /**
   * Transfer tokens from the caller to a new holder.
   * Remember, there's a 0.5% fee here as well.
   */
  function transfer(address _toAddress, uint256 _amountOfTokens)
  onlyBagholders()
  public
  returns(bool)
  {
    // setup
    address _customerAddress = msg.sender;

    // make sure we have the requested tokens
    // also disables transfers until ambassador phase is over
    // ( we dont want whale premines )
    require(!onlyAmbassadors && _amountOfTokens <= tokenBalanceLedger_[_customerAddress]);

    // withdraw all outstanding dividends first
    if(myDividends(true) > 0) withdraw();

    // liquify 0.5% of the tokens that are transfered
    // these are dispersed to shareholders
    uint256 _tokenFee = SafeMath.div(_amountOfTokens, 200);
    uint256 _taxedTokens = SafeMath.sub(_amountOfTokens, _tokenFee);
    uint256 _dividends = tokensToEthereum_(_tokenFee);

    // burn the fee tokens
    tokenSupply_ = SafeMath.sub(tokenSupply_, _tokenFee);

    // exchange tokens
    tokenBalanceLedger_[_customerAddress] = SafeMath.sub(tokenBalanceLedger_[_customerAddress], _amountOfTokens);
    tokenBalanceLedger_[_toAddress] = SafeMath.add(tokenBalanceLedger_[_toAddress], _taxedTokens);

    // update dividend trackers
    payoutsTo_[_customerAddress] -= (int256) (profitPerShare_ * _amountOfTokens);
    payoutsTo_[_toAddress] += (int256) (profitPerShare_ * _taxedTokens);

    // disperse dividends among holders
    profitPerShare_ = SafeMath.add(profitPerShare_, (_dividends * magnitude) / tokenSupply_);

    // fire event
    emit Transfer(_customerAddress, _toAddress, _taxedTokens);

    // ERC20
    return true;

  }

  /**
   * airdrop or donate eth to all holder
   */
  function airdrop()
  payable
  public
  {
    require(tokenSupply_ > 0, 'no holder');
    profitPerShare_ = SafeMath.add(profitPerShare_, (msg.value * magnitude) / tokenSupply_);
    emit onAirdrop(msg.sender, msg.value);
  }

  /*----------  ADMINISTRATOR ONLY FUNCTIONS  ----------*/
  /**
   * In case the amassador quota is not met, the administrator can manually disable the ambassador phase.
   */
  function disableInitialStage()
  onlyAdministrator()
  public
  {
    onlyAmbassadors = false;
  }

  /**
   * In case one of us dies, we need to replace ourselves.
   */
  function setAdministrator(bytes32 _identifier, bool _status)
  onlyAdministrator()
  public
  {
    administrators[_identifier] = _status;
  }

  /**
   * Precautionary measures in case we need to adjust the masternode rate.
   */
  function setStakingRequirement(uint256 _amountOfTokens)
  onlyAdministrator()
  public
  {
    stakingRequirement = _amountOfTokens;
  }

  /**
   * If we want to rebrand, we can.
   */
  function setName(string _name)
  onlyAdministrator()
  public
  {
    name = _name;
  }

  /**
   * If we want to rebrand, we can.
   */
  function setSymbol(string _symbol)
  onlyAdministrator()
  public
  {
    symbol = _symbol;
  }


  /*----------  HELPERS AND CALCULATORS  ----------*/
  /**
   * Retrieve the contract public state.
   * -function hash- 0xc19d93fb
   * @return total supply
   * @return total ethereum balance
   * @return current buy price of 1 individual token
   * @return current sell price of 1 individual token
   */
  function state()
  public
  view
  returns(uint256, uint, uint256, uint256)
  {
    return (
      tokenSupply_,           // 0
      totalEthereumBalance(), // 1
      buyPrice(),             // 2
      sellPrice()             // 3
    );
  }

  /**
   * Retrieve the token holder state.
   * @return token balance
   * @return dividends
   * @return referral bonus balance
   */
  function holderState()
  public
  view
  returns(uint256, uint256, uint256)
  {
    address _customerAddress = msg.sender;
    return (
      balanceOf(_customerAddress),          // 0
      dividendsOf(_customerAddress),        // 1
      referralBalance_[_customerAddress]    // 2
    );
  }

  /**
   * Method to view the current Ethereum stored in the contract.
   * Example: totalEthereumBalance()
   */
  function totalEthereumBalance()
  public
  view
  returns(uint)
  {
    return address(this).balance;
  }

  /**
   * Retrieve the total token supply.
   */
  function totalSupply()
  public
  view
  returns(uint256)
  {
    return tokenSupply_;
  }

  /**
   * Retrieve the tokens owned by the caller.
   */
  function myTokens()
  public
  view
  returns(uint256)
  {
    address _customerAddress = msg.sender;
    return balanceOf(_customerAddress);
  }

  /**
   * Retrieve the dividends owned by the caller.
   * If `_includeReferralBonus` is to to 1/true, the referral bonus will be included in the calculations.
   * The reason for this, is that in the frontend, we will want to get the total divs (global + ref)
   * But in the internal calculations, we want them separate.
   */
  function myDividends(bool _includeReferralBonus)
  public
  view
  returns(uint256)
  {
    address _customerAddress = msg.sender;
    return _includeReferralBonus ? dividendsOf(_customerAddress) + referralBalance_[_customerAddress] : dividendsOf(_customerAddress) ;
  }

  /**
   * Retrieve the token balance of any single address.
   */
  function balanceOf(address _customerAddress)
  view
  public
  returns(uint256)
  {
    return tokenBalanceLedger_[_customerAddress];
  }

  /**
   * Retrieve the dividend balance of any single address.
   */
  function dividendsOf(address _customerAddress)
  view
  public
  returns(uint256)
  {
    return (uint256) ((int256)(profitPerShare_ * tokenBalanceLedger_[_customerAddress]) - payoutsTo_[_customerAddress]) / magnitude;
  }

  /**
   * Return the buy price of 1 individual token.
   */
  function sellPrice()
  public
  view
  returns(uint256)
  {
    // our calculation relies on the token supply, so we need supply. Doh.
    if(tokenSupply_ == 0){
      return tokenPriceInitial_ - tokenPriceIncremental_;
    } else {
      uint256 _ethereum = tokensToEthereum_(1e18);
      uint256 _dividends = SafeMath.div(_ethereum, dividendFee_  );
      uint256 _taxedEthereum = SafeMath.sub(_ethereum, _dividends);
      return _taxedEthereum;
    }
  }

  /**
   * Return the sell price of 1 individual token.
   */
  function buyPrice()
  public
  view
  returns(uint256)
  {
    // our calculation relies on the token supply, so we need supply. Doh.
    if(tokenSupply_ == 0){
      return tokenPriceInitial_ + tokenPriceIncremental_;
    } else {
      uint256 _ethereum = tokensToEthereum_(1e18);
      uint256 _dividends = SafeMath.div(_ethereum, dividendFee_  );
      uint256 _taxedEthereum = SafeMath.add(_ethereum, _dividends);
      return _taxedEthereum;
    }
  }

  /**
   * Function for the frontend to dynamically retrieve the price scaling of buy orders.
   */
  function calculateTokensReceived(uint256 _ethereumToSpend)
  public
  view
  returns(uint256)
  {
    uint256 _dividends = SafeMath.div(_ethereumToSpend, dividendFee_);
    uint256 _taxedEthereum = SafeMath.sub(_ethereumToSpend, _dividends);
    uint256 _amountOfTokens = ethereumToTokens_(_taxedEthereum);

    return _amountOfTokens;
  }

  /**
   * Function for the frontend to dynamically retrieve the price scaling of sell orders.
   */
  function calculateEthereumReceived(uint256 _tokensToSell)
  public
  view
  returns(uint256)
  {
    require(_tokensToSell <= tokenSupply_);
    uint256 _ethereum = tokensToEthereum_(_tokensToSell);
    uint256 _dividends = SafeMath.div(_ethereum, dividendFee_);
    uint256 _taxedEthereum = SafeMath.sub(_ethereum, _dividends);
    return _taxedEthereum;
  }


  /*==========================================
  =            INTERNAL FUNCTIONS            =
  ==========================================*/
  function purchaseTokens(uint256 _incomingEthereum, address _referredBy)
  antiEarlyWhale(_incomingEthereum)
  internal
  returns(uint256)
  {
    // data setup
    address _customerAddress = msg.sender;
    uint256 _undividedDividends = SafeMath.div(_incomingEthereum, dividendFee_);
    uint256 _referralBonus = SafeMath.div(_undividedDividends, 3);
    uint256 _dividends = SafeMath.sub(_undividedDividends, _referralBonus);
    uint256 _taxedEthereum = SafeMath.sub(_incomingEthereum, _undividedDividends);
    uint256 _amountOfTokens = ethereumToTokens_(_taxedEthereum);
    uint256 _fee = _dividends * magnitude;

    // no point in continuing execution if OP is a poorfag russian hacker
    // prevents overflow in the case that the pyramid somehow magically starts being used by everyone in the world
    // (or hackers)
    // and yes we know that the safemath function automatically rules out the "greater then" equasion.
    require(_amountOfTokens > 0 && (SafeMath.add(_amountOfTokens, tokenSupply_) > tokenSupply_));

    // is the user referred by a masternode?
    if(
    // is this a referred purchase?
      _referredBy != 0x0000000000000000000000000000000000000000 &&

      // no cheating!
      _referredBy != _customerAddress &&

      // does the referrer have at least X whole tokens?
      // i.e is the referrer a godly chad masternode
      tokenBalanceLedger_[_referredBy] >= stakingRequirement
    ){
      // wealth redistribution
      referralBalance_[_referredBy] = SafeMath.add(referralBalance_[_referredBy], _referralBonus);
    } else {
      // no ref purchase
      // add the referral bonus back to the global dividends cake
      _dividends = SafeMath.add(_dividends, _referralBonus);
      _fee = _dividends * magnitude;
    }

    // we can't give people infinite ethereum
    if(tokenSupply_ > 0){

      // add tokens to the pool
      tokenSupply_ = SafeMath.add(tokenSupply_, _amountOfTokens);

      // take the amount of dividends gained through this transaction, and allocates them evenly to each shareholder
      profitPerShare_ += (_dividends * magnitude / (tokenSupply_));

      // calculate the amount of tokens the customer receives over his purchase
      _fee = _fee - (_fee-(_amountOfTokens * (_dividends * magnitude / (tokenSupply_))));

    } else {
      // add tokens to the pool
      tokenSupply_ = _amountOfTokens;
    }

    // update circulating supply & the ledger address for the customer
    tokenBalanceLedger_[_customerAddress] = SafeMath.add(tokenBalanceLedger_[_customerAddress], _amountOfTokens);

    // Tells the contract that the buyer doesn't deserve dividends for the tokens before they owned them;
    // really i know you think you do but you don't
    int256 _updatedPayouts = (int256) ((profitPerShare_ * _amountOfTokens) - _fee);
    payoutsTo_[_customerAddress] += _updatedPayouts;

    // fire event
    emit onTokenPurchase(_customerAddress, _incomingEthereum, _amountOfTokens, _referredBy);

    return _amountOfTokens;
  }

  /**
   * Calculate Token price based on an amount of incoming ethereum
   * It's an algorithm, hopefully we gave you the whitepaper with it in scientific notation;
   * Some conversions occurred to prevent decimal errors or underflows / overflows in solidity code.
   */
  function ethereumToTokens_(uint256 _ethereum)
  internal
  view
  returns(uint256)
  {
    uint256 _tokenPriceInitial = tokenPriceInitial_ * 1e18;
    uint256 _tokensReceived =
    (
    (
    // underflow attempts BTFO
    SafeMath.sub(
      (sqrt
    (
      (_tokenPriceInitial**2)
      +
      (2*(tokenPriceIncremental_ * 1e18)*(_ethereum * 1e18))
      +
      (((tokenPriceIncremental_)**2)*(tokenSupply_**2))
      +
      (2*(tokenPriceIncremental_)*_tokenPriceInitial*tokenSupply_)
    )
      ), _tokenPriceInitial
    )
    )/(tokenPriceIncremental_)
    )-(tokenSupply_)
    ;

    return _tokensReceived;
  }

  /**
   * Calculate token sell value.
   * It's an algorithm, hopefully we gave you the whitepaper with it in scientific notation;
   * Some conversions occurred to prevent decimal errors or underflows / overflows in solidity code.
   */
  function tokensToEthereum_(uint256 _tokens)
  internal
  view
  returns(uint256)
  {

    uint256 tokens_ = (_tokens + 1e18);
    uint256 _tokenSupply = (tokenSupply_ + 1e18);
    uint256 _etherReceived =
    (
    // underflow attempts BTFO
    SafeMath.sub(
      (
      (
      (
      tokenPriceInitial_ +(tokenPriceIncremental_ * (_tokenSupply/1e18))
      )-tokenPriceIncremental_
      )*(tokens_ - 1e18)
      ),(tokenPriceIncremental_*((tokens_**2-tokens_)/1e18))/2
    )
    /1e18);
    return _etherReceived;
  }


  //This is where all your gas goes, sorry.
  //Not sorry, you probably only paid 1 gwei
  function sqrt(uint x) internal pure returns (uint y) {
    uint z = (x + 1) / 2;
    y = x;
    while (z < y) {
      y = z;
      z = (x / z + z) / 2;
    }
  }
}

/**
 * @title SafeMath
 * @dev Math operations with safety checks that throw on error
 */
library SafeMath {

  /**
  * @dev Multiplies two numbers, throws on overflow.
  */
  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    assert(c / a == b);
    return c;
  }

  /**
  * @dev Integer division of two numbers, truncating the quotient.
  */
  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  /**
  * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
  */
  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  /**
  * @dev Adds two numbers, throws on overflow.
  */
  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}