contract KiiADefi{

 string public name = "KiiA Token Farm";
 address public onlyOwner;
 KiiAToken public kiiaToken;
 using SafeMath for uint256;

 using BokkyPooBahsDateTimeLibrary for uint;
 
 function _diffDays(uint fromTimestamp, uint toTimestamp) public pure returns (uint _days) {
 _days = BokkyPooBahsDateTimeLibrary.diffDays(fromTimestamp, toTimestamp);
 }

 function _getTotalDays(uint _months) public view returns(uint){
 uint fromday = now;
 uint today = fromday.addMonths(_months);
 uint pendindays = _diffDays(fromday,today);
 return (pendindays);
 }

 //Deposit structure to hold all the variables
 struct Deposits {
 uint deposit_id;
 address investorAddress;
 uint planid;
 uint plantype;
 uint month;
 uint interest;
 uint256 invested;
 uint256 totalBonusToReceive;
 uint256 principalPmt;
 uint256 dailyEarnings;
 uint nthDay;
 uint daysToClose;
 bool isUnlocked;
 bool withdrawn;
 }
 
 //PlanDetails structure
 struct PlanDetails {
 uint planid;
 uint month;
 uint interest;
 uint plantype;
 bool isActive;
 }
 
 //Events to capture deposit event
 event addDepositEvent(
 uint depositId,
 address investorAddress
 );
 
 //Events to capture add plan
 event addPlanEvent(
 uint index,
 uint planid
 );
 
 //Events to capture update/edit plan
 event updateorEditPlanEvent(
 uint planid
 );
 
 event dummyEvent(
 address text1, 
 bytes32 text2
 );
 
 event dummyEventint(
 uint text1, 
 bool text2,
 uint text3
 );
 
 //Events to capture whitelist event
 event whiteListEvent(
 address owner,
 address investor
 );
 
 //Event to capture calculate bonus
 event calculateBonusEvent(
 uint depositid,
 address investorAddress
 );
 
 //Events to capture unlock event
 event addUnlockEvent(
 uint indexed _id,
 address _investorAddress,
 uint _planid
 ); 
 
 //Events to capture lock event
 event addlockEvent(
 uint indexed _id,
 address _investorAddress,
 uint _planid
 ); 
 
 //Events to capture Withdraw event 
 event addWithdrawEvent(
 uint indexed _id,
 address _investorAddress,
 uint _planid
 ); 
 
 uint public depositCounter;
 uint public planCounter;
 
 Deposits[] public allDeposits;
 PlanDetails[] public allPlanDetails;
 
 //to view deposit information
 mapping(address=>Deposits[]) public depositDetails;
 mapping(address=>mapping(uint=>Deposits[])) public viewDeposit;

 mapping(address => bool) public whitelistAddresses;
 address[] public whiteListed;
 address[] public stakers;
 mapping(address => bool) public hasStaked;
 
 //address -> plan -> staking or not
 mapping(address => mapping(uint => bool)) public isStaking;
 
 //plan active state
 mapping(uint =>bool) public isPlanActive;

 constructor(KiiAToken _kiiaToken, address _owneraddr) public payable {
 kiiaToken = _kiiaToken;
 onlyOwner = _owneraddr;
 }
 
 function addEth() public payable {
 //function to accept ether 
 }
 
 //Function to whitelist address
 function whiteListIt(address _beneficiary) public returns(uint) {
 // Only owner can call this function
 require(msg.sender == onlyOwner, "caller must be the owner");
 require(whitelistAddresses[_beneficiary]==false, "Already whitelisted");
 whitelistAddresses[_beneficiary] = true;
 whiteListed.push(_beneficiary);
 emit whiteListEvent(msg.sender,_beneficiary);
 return 0;
 }
 
 //Function to whitelist address in bulk fashion
 function bulkwhiteListIt(address[] memory _beneficiary) public returns(uint) {
 // Only owner can call this function
 require(msg.sender == onlyOwner, "caller must be the owner");
 uint tot = _beneficiary.length;
 if(tot<=255){
 for(uint i=0;i<tot; i++){
 if(!whitelistAddresses[_beneficiary[i]]){
 whitelistAddresses[_beneficiary[i]] = true;
 whiteListed.push(_beneficiary[i]);
 emit whiteListEvent(msg.sender,_beneficiary[i]);
 }
 }
 return 0; 
 }
 }
 
 //Function to bulk remove from bulkremoveFromwhiteListIt
 function bulkremoveFromwhiteListIt(address[] memory _beneficiary) public returns(uint) {
 // Only owner can call this function
 require(msg.sender == onlyOwner, "caller must be the owner");
 uint tot = _beneficiary.length;
 if(tot<=255){
 for(uint i=0;i<tot; i++){
 if(!whitelistAddresses[_beneficiary[i]]){
 whitelistAddresses[_beneficiary[i]] = false;
 whiteListed.push(_beneficiary[i]);
 emit whiteListEvent(msg.sender,_beneficiary[i]);
 }
 }
 return 0; 
 }
 }
 
 //remove from whiteList
 function removefromWhiteList(address _beneficiary) public returns(uint) {
 // Only owner can call this function
 require(msg.sender == onlyOwner, "caller must be the owner");
 require(whitelistAddresses[_beneficiary]==true, "Already in graylist");
 whitelistAddresses[_beneficiary] = false;
 emit whiteListEvent(msg.sender,_beneficiary);
 return 0;
 }
 
 //Getter Function for getplan by id
 function getPlanById(uint _planid) public view returns(uint plan_id,uint month,uint interest,uint plantype,bool isActive){
 uint tot = allPlanDetails.length;
 for(uint i=0;i<tot;i++){
 if(allPlanDetails[i].planid==_planid){
 return(allPlanDetails[i].planid,allPlanDetails[i].month,allPlanDetails[i].interest,allPlanDetails[i].plantype,allPlanDetails[i].isActive);
 }
 }
 }
 
 //Getter Function for getplan by id
 function getPlanDetails(uint _planid) internal view returns(uint month,uint interest,uint plantype){
 uint tot = allPlanDetails.length;
 for(uint i=0;i<tot;i++){
 if(allPlanDetails[i].planid==_planid){
 return(allPlanDetails[i].month,allPlanDetails[i].interest,allPlanDetails[i].plantype);
 }
 }

 }
 
 //this function is to avoid stack too deep error
 function _deposits(uint _month, uint _amount, uint256 _interest) internal view returns (uint _nthdayv2,uint _pendingDaysv2,uint256 _totalBonusToReceivev2,uint256 _dailyEarningsv2,uint _principalPmtDailyv2) {
 uint256 _pendingDaysv1 = _getTotalDays(_month);
 uint256 _interesttoDivide = _interest.mul(1000000).div(100) ;
 uint256 _totalBonusToReceivev1 = _amount.mul(_interesttoDivide).div(1000000);
 uint _nthdayv1 = 0;
 uint _principalPmtDaily = 0;
 uint _dailyEarningsv1 = 0;
 return (_nthdayv1,_pendingDaysv1,_totalBonusToReceivev1,_dailyEarningsv1,_principalPmtDaily);
 } 
 
 function depositTokens(uint _plan,uint256 _plandate, uint _amount) public{
 // check if user is whitelisted
 require(whitelistAddresses[msg.sender]==true,"Only whitelisted user is allowed to deposit tokens");
 require(_amount > 0, "amount cannot be 0");
 require(isPlanActive[_plan]==true,"Plan is not active"); // To check if plan is active 
 
 (uint _month,uint _interest,uint _plantype) = getPlanDetails(_plan);
 
 require(_interest > 0, "interest rate cannot be 0");
 require(_month > 0,"_months cannot be 0");
 
 // Trasnfer kiiA tokens to this contract for staking
 kiiaToken.transferFrom(msg.sender, address(this), _amount);
 
 //scope to remove the error Stack too deep
 (uint _nthday,uint _daystoclose,uint _totalBonusToReceive,uint _dailyEarnings,uint _principalPmtDaily) = _deposits(_month,_amount,_interest);
 
 uint _localid = allDeposits.length++;
 //deposit token in defi
 allDeposits[allDeposits.length-1] = Deposits(_plandate, 
 msg.sender,
 _plan,
 _plantype,
 _month,
 _interest,
 _amount,
 _totalBonusToReceive,
 _principalPmtDaily,
 _dailyEarnings,
 _nthday,
 _daystoclose,
 false,
 false
 );
 
 //Add Deposit details
 depositDetails[msg.sender].push(allDeposits[allDeposits.length-1]);
 
 //is Staking in this plan 
 isStaking[msg.sender][_plandate] = true;

 // Add user to stakers array *only* if they haven't staked already
 if(!hasStaked[msg.sender]) {
 stakers.push(msg.sender);
 } 
 hasStaked[msg.sender] = true;
 
 emit addDepositEvent(_localid, msg.sender);
 
 }
 
 //Setter function for plan
 function registerPlan(uint _planid, uint _month,uint _interest,uint _plantype) public returns(uint){
 require(msg.sender == onlyOwner, "caller must be the owner");
 require(_planid > 0, "Plan Id cannot be 0");
 require(_month > 0, "Month cannot be 0");
 require(_interest > 0, "Interest cannot be 0");
 require(_plantype >= 0, "Plantype can be either 0 or 1");
 require(_plantype <= 1, "Plantype can be either 0 or 1");
 require(isPlanActive[_planid]==false,"Plan already exists in active status"); 

 planCounter = planCounter + 1;
 uint _localid = allPlanDetails.length++;
 allPlanDetails[allPlanDetails.length-1] = PlanDetails(_planid,
 _month,
 _interest,
 _plantype,
 true
 ); 
 isPlanActive[_planid] = true;
 emit addPlanEvent(_localid,_planid);
 return 0;
 }
 
 //Setter function for plan
 function updatePlan(uint _planid, uint _month,uint _interest,uint _plantype) public returns(uint){
 require(msg.sender == onlyOwner, "caller must be the owner");
 require(_planid > 0, "Plan Id cannot be 0");
 require(_month > 0, "Month cannot be 0");
 require(_interest > 0, "Interest cannot be 0");

 uint tot = allPlanDetails.length;
 for(uint i=0;i<tot;i++){
 if(allPlanDetails[i].planid==_planid){
 allPlanDetails[i].month = _month;
 allPlanDetails[i].interest = _interest;
 allPlanDetails[i].plantype = _plantype;
 }
 }
 emit updateorEditPlanEvent(_planid);
 return 0;
 }
 
 //Deactivate plan
 function deactivatePlan(uint _planid) public returns(uint){
 require(msg.sender == onlyOwner, "caller must be the owner");
 require(isPlanActive[_planid]==true, "Plan already deactivated");
 isPlanActive[_planid]= false;
 emit updateorEditPlanEvent(_planid);
 return 0;
 }
 
 //Reactivate plan
 function reactivatePlan(uint _planid) public returns(uint){
 require(msg.sender == onlyOwner, "caller must be the owner");
 require(isPlanActive[_planid]==false, "Plan already activated");
 isPlanActive[_planid]= true;
 emit updateorEditPlanEvent(_planid);
 return 0;
 }
 
 //To calculate bonus - this function should be called once per day by owner
 function calcBonus() public returns(uint){
 require(msg.sender == onlyOwner, "caller must be the owner");
 uint totDep = allDeposits.length;
 for(uint i=0; i<totDep;i++){
 uint _plantype = allDeposits[i].plantype;
 uint _nthDay = allDeposits[i].nthDay;
 uint _invested = allDeposits[i].invested;
 uint _daysToClose= allDeposits[i].daysToClose;
 uint _principalPmt = _invested.div(_daysToClose);

 //check if already withdrawn, if yes, then dont calculate
 bool _withdrawn = allDeposits[i].withdrawn;
 emit dummyEventint(_plantype,_withdrawn,0);
 if(_plantype==0){
 if(_nthDay<_daysToClose){
 allDeposits[i].nthDay = _nthDay.add(1);
 allDeposits[i].principalPmt = allDeposits[i].principalPmt + _principalPmt;
 //emit event
 emit calculateBonusEvent(allDeposits[i].deposit_id,allDeposits[i].investorAddress);
 } 
 }
 if(_plantype==1){
 if(_nthDay<_daysToClose){
 allDeposits[i].nthDay = _nthDay.add(1);
 allDeposits[i].principalPmt = allDeposits[i].principalPmt + _principalPmt;
 //emit event
 emit calculateBonusEvent(allDeposits[i].deposit_id,allDeposits[i].investorAddress);
 }
 }
 }

 }
 
 //Get deposit by address
 uint[] depNewArray;
 function getDepositidByAddress(address _beneficiary) public returns(uint[] memory){
 uint tot = allDeposits.length;
 uint[] memory tmparray;
 depNewArray = tmparray;
 for(uint i =0; i< tot; i++){
 if(allDeposits[i].investorAddress==_beneficiary){
 depNewArray.push(allDeposits[i].deposit_id);
 }
 }
 return depNewArray;
 }
 
 
 function getDepositByAddress(address _beneficiary,uint _deposit_id) public view returns(uint256,uint,uint,uint,uint,uint256,uint256,uint,uint,uint,bool){
 uint tot = allDeposits.length;
 for(uint i=0;i<tot;i++){
 if(_beneficiary==allDeposits[i].investorAddress){
 if(allDeposits[i].deposit_id==_deposit_id){
 return(allDeposits[i].invested,
 allDeposits[i].planid,
 allDeposits[i].plantype,
 allDeposits[i].month,
 allDeposits[i].interest,
 allDeposits[i].totalBonusToReceive,
 allDeposits[i].dailyEarnings,
 allDeposits[i].principalPmt,
 allDeposits[i].daysToClose,
 allDeposits[i].nthDay,
 allDeposits[i].isUnlocked
 );
 }
 }
 }
 }
 
 // Unlock address
 function setLock(address _beneficiary,uint _deposit_id) public returns(uint) {
 // Only owner can call this function
 require(msg.sender == onlyOwner, "caller must be the owner");
 // set lock
 uint totDep = allDeposits.length;
 for(uint i=0;i<totDep; i++){
 if(allDeposits[i].investorAddress==_beneficiary){
 if (allDeposits[i].deposit_id==_deposit_id){
 allDeposits[i].isUnlocked = false;
 emit addlockEvent(allDeposits[i].deposit_id,allDeposits[i].investorAddress,allDeposits[i].planid);
 }
 }
 }
 return 0;
 }
 
 // Unlock address
 function unlock(address _beneficiary,uint _deposit_id) public returns(uint) {
 // Only owner can call this function
 require(msg.sender == onlyOwner, "caller must be the owner");
 // Unlock
 uint totDep = allDeposits.length;
 for(uint i=0;i<totDep; i++){
 if(allDeposits[i].investorAddress==_beneficiary){
 if (allDeposits[i].deposit_id==_deposit_id){
 allDeposits[i].isUnlocked = true;
 emit addUnlockEvent(allDeposits[i].deposit_id,allDeposits[i].investorAddress,allDeposits[i].planid);
 }
 }
 }
 return 0;
 }

 // Bulk Unlock address
 function bulkunlock(address[] memory _beneficiary, uint256[] memory _deposit_id) public returns(uint) {
 // Only owner can call this function
 require(msg.sender == onlyOwner, "caller must be the owner");
 require(_beneficiary.length == _deposit_id.length,"Array length must be equal");
 // Unlock
 uint totDep = allDeposits.length;
 for (uint j = 0; j < _beneficiary.length; j++) {
 for(uint i=0;i<totDep; i++){
 if(allDeposits[i].investorAddress==_beneficiary[j]){
 if (allDeposits[i].deposit_id==_deposit_id[j]){
 allDeposits[i].isUnlocked = true;
 }
 }
 }
 }
 return 0;
 }

 // Bring staker list Getter function
 function stakerlist() public view returns(address[] memory){
 return stakers;
 }
 
 // Bring whitelisted addresss list 
 address[] whiteArray;
 function whiteListedAddress() public returns(address[] memory){
 uint tot = whiteListed.length;
 address[] memory tmparray;
 whiteArray = tmparray;
 for(uint i=0;i<tot; i++){
 whiteArray.push(whiteListed[i]);
 emit dummyEvent(whiteListed[i],"testing");
 }
 return whiteArray;
 }
 
 // Bring bloacklisted addresss list 
 address[] blackArray;
 function blackListedAddress() public returns(address[] memory){
 uint tot = whiteListed.length;
 address[] memory tmparray;
 blackArray = tmparray;
 for(uint i=0;i<tot; i++){
 if(whitelistAddresses[whiteListed[i]]==false){
 blackArray.push(whiteListed[i]);
 }
 }
 return blackArray;
 }
 
 // Unstaking Tokens (Withdraw)
 function withDrawTokens(uint _deposit_id, uint _plantype) public returns(uint) {
 uint totDep = allDeposits.length;
 for(uint i=0;i<totDep; i++){
 if(allDeposits[i].investorAddress==msg.sender){
 if (allDeposits[i].deposit_id==_deposit_id){
 require(allDeposits[i].invested > 0, "Staking balance cannot be 0");
 require(allDeposits[i].withdrawn==false, "Plan is already withdrawn by user");
 require(allDeposits[i].isUnlocked==true, "User account must be unlocked by owner to withdraw");
 require(isStaking[msg.sender][_deposit_id]==true,"User is not staking any amount in this plan");
 uint balance = allDeposits[i].invested;
 
 //Regular Plan withdrawal
 if(_plantype==0){
 uint _principalPmt = allDeposits[i].principalPmt;
 uint _toTransfer1 = balance.sub(_principalPmt);
 // Transfer back KiiA tokens to this address
 kiiaToken.transfer(msg.sender, _toTransfer1);
 allDeposits[i].principalPmt = _principalPmt.add(_toTransfer1);
 allDeposits[i].totalBonusToReceive = 0;
 allDeposits[i].withdrawn = true;
 isStaking[msg.sender][_deposit_id]=false;
 emit addWithdrawEvent(allDeposits[i].deposit_id,allDeposits[i].investorAddress,allDeposits[i].planid);
 return 0;
 }
 
 //Fixed Plan withdrawal
 if(_plantype==1){
 uint nthDay = allDeposits[i].nthDay;
 uint dailyEarnings = allDeposits[i].dailyEarnings;
 uint256 _interestAccumulated = nthDay.mul(dailyEarnings);
 uint256 _toTransfer2 = balance.add(_interestAccumulated);
 // Transfer back KiiA tokens to this address
 kiiaToken.transfer(msg.sender, _toTransfer2);
 allDeposits[i].totalBonusToReceive = 0;
 allDeposits[i].withdrawn = true;
 isStaking[msg.sender][_deposit_id]=false;
 emit addWithdrawEvent(allDeposits[i].deposit_id,allDeposits[i].investorAddress,allDeposits[i].planid);
 return 0;
 }
 }
 }
 }
 }
}
function bulkwhiteListIt(address[] memory _beneficiary) public returns(uint) {
// Only owner can call this function
require(msg.sender == onlyOwner, "caller must be the owner");
uint tot = _beneficiary.length;
if(tot<=255){
for(uint i=0;i<tot; i++){
if(!whitelistAddresses[_beneficiary[i]]){
whitelistAddresses[_beneficiary[i]] = true;
whiteListed.push(_beneficiary[i]);
emit whiteListEvent(msg.sender,_beneficiary[i]);
}
}
return 0; 
}
}

//Function to whitelist address in bulk fashion