contract ContractNo1 is Auth {
  using SafeMath for uint;

  enum PackageType {
    M0,
    M3,
    M6,
    M9,
    M12,
    M15,
    M18
  }

  enum WithdrawType {
    Half,
    Full
  }

  mapping(address => bool) public lAS;
  IERC20 public dabToken = IERC20(0x5E7Ebea68ab05198F771d77a875480314f1d0aae);
  IContractNo2 public contractNo2;
  IContractNo3 public contractNo3;

  uint public minJP = 5e18;
  uint8 public gemJPPercent = 30;

  event Registered(uint id, string userName, address userAddress, address inviter);
  event PackageJoined(address indexed from, address indexed to, PackageType packageType, uint dabAmount, uint gemAmount);
  event Profited(address indexed user, uint dabAmount, uint gemAmount);
  event Withdrew(address indexed user, uint dabAmount, uint gemAmount);

  constructor(
    address _backupAdmin,
    address _mainAdmin,
    address _dabAdmin,
    address _LAdmin
  )
  public
  Auth(
    _backupAdmin,
    _mainAdmin,
    msg.sender,
    _dabAdmin,
    address(0x0),
    _LAdmin
  ) {
  }

  // ADMINS FUNCTIONS

  function setC(address _c) onlyContractAdmin public {
    contractNo3 = IContractNo3(_c);
  }

  function setW(address _w) onlyContractAdmin public {
    contractNo2 = IContractNo2(_w);
  }

  function updateBackupAdmin(address _newBackupAdmin) onlyBackupAdmin public {
    require(_newBackupAdmin != address(0x0), 'Invalid address');
    backupAdmin = _newBackupAdmin;
  }

  function updateMainAdmin(address _newMainAdmin) onlyBackupOrMainAdmin public {
    require(_newMainAdmin != address(0x0), 'Invalid address');
    mainAdmin = _newMainAdmin;
  }

  function updateContractAdmin(address _newContractAdmin) onlyMainAdmin public {
    require(_newContractAdmin != address(0x0), 'Invalid address');
    contractAdmin = _newContractAdmin;
  }

  function updateDABAdmin(address _newDABAdmin) onlyMainAdmin public {
    require(_newDABAdmin != address(0x0), 'Invalid address');
    dabAdmin = _newDABAdmin;
  }

  function updateLockerAdmin(address _newLockerAdmin) onlyMainAdmin public {
    require(_newLockerAdmin != address(0x0), 'Invalid address');
    LAdmin = _newLockerAdmin;
  }

  function LA(address[] _values, bool _locked) onlyLAdmin public {
    require(_values.length > 0, 'Values cannot be empty');
    require(_values.length <= 256, 'Maximum is 256');
    for (uint8 i = 0; i < _values.length; i++) {
      require(_values[i] != msg.sender, 'Yourself!!!');
      lAS[_values[i]] = _locked;
    }
  }

  function setMinJP(uint _minJP) onlyMainAdmin public {
    require(_minJP > 0, 'Must be > 0');
    minJP = _minJP;
  }

  function setGemJP(uint8 _gemJPPercent) onlyMainAdmin public {
    require(0 < _gemJPPercent && _gemJPPercent < 101, 'Must be 1 - 100');
    gemJPPercent = _gemJPPercent;
  }

  // PUBLIC FUNCTIONS

  function register(string memory _userName, address _inviter) public {
    require(contractNo3.isCitizen(_inviter), 'Inviter did not registered');
    require(_inviter != msg.sender, 'Cannot referral yourself');
    uint id = contractNo3.register(msg.sender, _userName, _inviter);
    emit Registered(id, _userName, msg.sender, _inviter);
  }

  function showMe() public view returns (uint, string memory, address, address[], address[], address[], uint, uint, uint, uint, uint) {
    return contractNo3.showInvestorInfo(msg.sender);
  }

  function joinPackage(address _to, PackageType _type, uint _dabAmount, uint _gemAmount) public {
    uint packageAmount = _dabAmount.add(_gemAmount);
    validateJoinPackage(msg.sender, _to, _type, _dabAmount, _gemAmount);
    require(packageAmount >= minJP, 'Package amount must be greater min');
    require(dabToken.allowance(msg.sender, address(this)) >= _dabAmount, 'Please call approve() first');
    require(dabToken.balanceOf(msg.sender) >= _dabAmount, 'You have not enough funds');
    if (_gemAmount > 0) {
      uint8 gemPercent = uint8(_gemAmount.mul(100).div(packageAmount));
      require(gemPercent <= gemJPPercent, 'Too much GEM');
      contractNo2.adminCommission(_gemAmount.div(5));
    }

    require(dabToken.transferFrom(msg.sender, address(this), _dabAmount), 'Transfer token to contract failed');

    contractNo2.deposit(_to, uint8(_type), packageAmount, _dabAmount, _gemAmount);

    require(dabToken.transfer(dabAdmin, _dabAmount.div(5)), 'Transfer token to admin failed');

    emit PackageJoined(msg.sender, _to, _type, _dabAmount, _gemAmount);
  }

  function profit() public {
    require(!lAS[msg.sender], 'You can\'t do this now');
    uint dabProfit;
    uint gemProfit;
    (dabProfit, gemProfit) = contractNo2.getProfit(msg.sender, dabToken.balanceOf(address(this)));
    require(dabToken.transfer(msg.sender, dabProfit), 'Transfer profit to user failed');
    emit Profited(msg.sender, dabProfit, gemProfit);
  }

  function withdraw(WithdrawType _type) public {
    require(!lAS[msg.sender], 'You can\'t do this now');
    uint dabWithdrawable;
    uint gemWithdrawable;
    (dabWithdrawable, gemWithdrawable) = contractNo2.getWithdraw(msg.sender, dabToken.balanceOf(address(this)), uint8(_type));
    require(dabToken.transfer(msg.sender, dabWithdrawable), 'Transfer token to user failed');
    emit Withdrew(msg.sender, dabWithdrawable, gemWithdrawable);
  }

  // PRIVATE FUNCTIONS

  function validateJoinPackage(address _from, address _to, PackageType _type, uint _dabAmount, uint _gemAmount) private {
    require(contractNo3.isCitizen(_from), 'Please register first');
    require(contractNo3.isCitizen(_to), 'You can only active an exists member');
    if (_from != _to) {
      require(contractNo3.checkInvestorsInTheSameReferralTree(_from, _to), 'This user isn\'t in your referral tree');
    }
    require(contractNo2.validateJoinPackage(_from, _to, uint8(_type), _dabAmount, _gemAmount), 'Type or amount is invalid');
  }
}
function setC(address _c) onlyContractAdmin public {
  contractNo3 = IContractNo3(_c);
}

// ADMINS FUNCTIONS