contract NewTokenSale is Ownable,Pausable, Utils,Sales{

    GACToken token;
    bool fundssent;
    uint256 public tokensPerUSD;
    uint256 public currentSupply = 634585000000000000000000;
    PricingStrategy pricingstrategy;
    uint256 public tokenCreationMax = 275 * (10**6) * 10**18;

    ///the address of owner to recieve the token
    address public ownerAddr =0xB0583785f27B7f87535B4c574D3B30928aD3A7eb ; //to be filled

    ///this is the address of the distributong account admin
    address public distributorAddress = 0x5377209111cBe0cfeeaA54c4C28465cbf81D5601;

    ////MAX Tokens for private sale
    uint256 public maxPrivateSale = 150 * (10**6) * (10**18);
    ///MAX tokens for presale 
    uint256 public maxPreSale = 100 * (10**6) * (10**18);

    ///MAX tokens for the public sale
    uint256 public maxPublicSale = 20* (10**6) * (10**18);

    ///current sales
    uint256 public endprivate = 1525219200; // 05/02/2018 @ 12:00am (UTC)
    uint256 public endpresale = 1527724800;//05/31/2018 @ 12:00am (UTC)
    // uint256 public endpublicsale;
    uint256 public currentPrivateSale = 630585000000000000000000;
    uint256 public currentPreSale = 4000000000000000000000;
    uint256 public currentPublicSale ; 


    ///array of addresses for the ethereum relateed back funding  contract
    uint256  public numberOfBackers;

    mapping(uint256 => bool) transactionsClaimed;
    uint256 public valueToBeSent;
    uint public investorCount;

    struct balanceStruct{
        uint256 value;
        bool tokenstransferred;
    }

    mapping(address => balanceStruct) public balances;
    address[] public balancesArr;

    ///the event log to log out the address of the multisig wallet
    event logaddr(address addr);

    ///the function to get the balance
    function getBalance(address addr) public view returns(uint256) {
        return balances[addr].value;
    }

    ///the function of adding to balances
    function addToBalances(address addr, uint256 tokenValue) internal{
        balances[addr].value = SafeMath.add(balances[addr].value,tokenValue);
        bool found;
        for(uint i=0;i<balancesArr.length;i++){
            if(balancesArr[i]==addr){
                found = true;
            }
        }
        if(!found){
            balancesArr.push(addr);
        }
    }

    ///the function of adding to the balances
    function alottMainSaleToken(address[] arr) public {
        require(msg.sender == distributorAddress);
        for(uint i=0;i<arr.length;i++){
            if(checkExistsInArray(arr[i])){
            if(!balances[arr[i]].tokenstransferred){
                balances[arr[i]].tokenstransferred = true;
                token.addToBalances(arr[i], balances[arr[i]].value);
            }
        }
        }
    }

    function checkExistsInArray(address addr) internal returns (bool) {
        for(uint i=0;i<balancesArr.length;i++){
            if(balancesArr[i]==addr){
                return true;
            }
        }
        return false;
    }

    //the constructor function
   function NewTokenSale(address tokenAddress,address strategy){
        //require(bytes(_name).length > 0 && bytes(_symbol).length > 0); // validate input
        token = GACToken(tokenAddress);
        tokensPerUSD = 10 * 10 ** 18;
        valueToBeSent = token.valueToBeSent();
        pricingstrategy = PricingStrategy(strategy);
    }

    /**
        Payable function to send the ether funds
    **/
    function() external payable stopInEmergency{
        require(token.isValid());
        require(msg.value>0);
        ICOSaleState currentState = getStateFunding();
        require(currentState!=ICOSaleState.Failed);
        require(currentState!=ICOSaleState.Success);
        var (discount,usd) = pricingstrategy.totalDiscount(currentState,msg.value,"ethereum");
        uint256 tokens = usd*tokensPerUSD;
        uint256 totalTokens = SafeMath.add(tokens,SafeMath.div(SafeMath.mul(tokens,discount),100));
        if(currentState==ICOSaleState.PrivateSale){
            require(SafeMath.add(currentPrivateSale,totalTokens)<=maxPrivateSale);
            currentPrivateSale = SafeMath.add(currentPrivateSale,totalTokens);
        }else if(currentState==ICOSaleState.PreSale){
            require(SafeMath.add(currentPreSale,totalTokens)<=maxPreSale);
            currentPreSale = SafeMath.add(currentPreSale,totalTokens);
        }else if(currentState==ICOSaleState.PublicSale){
            require(SafeMath.add(currentPublicSale,totalTokens)<=maxPublicSale);
            currentPublicSale = SafeMath.add(currentPublicSale,totalTokens);
        }
        currentSupply = SafeMath.add(currentSupply,totalTokens);
        require(currentSupply<=tokenCreationMax);
        addToBalances(msg.sender,totalTokens);
        token.increaseEthRaised(msg.value);
        token.increaseUSDRaised(usd);
        numberOfBackers++;
        if(!ownerAddr.send(this.balance))throw;
    }
    
    //Token distribution for the case of the ICO
    ///function to run when the transaction has been veified
    function processTransaction(bytes txn, uint256 txHash,address addr,bytes20 btcaddr)  onlyOwner returns (uint)
    {   
        bool  valueSent;
        require(token.isValid());
     ICOSaleState currentState = getStateFunding();

        if(!transactionsClaimed[txHash]){
            var (a,b) = BTC.checkValueSent(txn,btcaddr,valueToBeSent);
            if(a){
                valueSent = true;
                transactionsClaimed[txHash] = true;
                 ///since we are creating tokens we need to increase the total supply
               allottTokensBTC(addr,b,currentState);
                return 1;
               }
        }
    }
    
    ///function to allot tokens to address
    function allottTokensBTC(address addr,uint256 value,ICOSaleState state) internal{
        ICOSaleState currentState = getStateFunding();
        require(currentState!=ICOSaleState.Failed);
        require(currentState!=ICOSaleState.Success);
        var (discount,usd) = pricingstrategy.totalDiscount(state,value,"bitcoin");
        uint256 tokens = usd*tokensPerUSD;
        uint256 totalTokens = SafeMath.add(tokens,SafeMath.div(SafeMath.mul(tokens,discount),100));
        if(currentState==ICOSaleState.PrivateSale){
            require(SafeMath.add(currentPrivateSale,totalTokens)<=maxPrivateSale);
            currentPrivateSale = SafeMath.add(currentPrivateSale,totalTokens);
        }else if(currentState==ICOSaleState.PreSale){
            require(SafeMath.add(currentPreSale,totalTokens)<=maxPreSale);
            currentPreSale = SafeMath.add(currentPreSale,totalTokens);
        }else if(currentState==ICOSaleState.PublicSale){
            require(SafeMath.add(currentPublicSale,totalTokens)<=maxPublicSale);
            currentPublicSale = SafeMath.add(currentPublicSale,totalTokens);
        }
       currentSupply = SafeMath.add(currentSupply,totalTokens);
       require(currentSupply<=tokenCreationMax);
       addToBalances(addr,totalTokens);
       token.increaseBTCRaised(value);
       token.increaseUSDRaised(usd);
       numberOfBackers++;
    }


    ///function to alott tokens by the owner

    function alottTokensExchange(address contributor,uint256 value) public onlyOwner{
        token.addToBalances(contributor,value);
        currentSupply = SafeMath.add(currentSupply,value);
    }

    function finalizeTokenSale() public onlyOwner{
        ICOSaleState currentState = getStateFunding();
        if(currentState!=ICOSaleState.Success) throw;
        token.finalizeICO();
    }

    ////kill the contract
    function killContract() public onlyOwner{
        selfdestruct(ownerAddr);
    }


    ///change the end private sale
    function changeEndPrivateSale(uint256 _newend) public onlyOwner{
        endprivate = _newend;
    }

    function changeEndPreSale(uint256 _newend) public onlyOwner{
        endpresale  = _newend;
    }


    function changeTokensPerUSD(uint256 _val) public onlyOwner{
        tokensPerUSD = _val;
    }

    function getStateFunding() returns (ICOSaleState){
       if(now>token.fundingStartBlock() && now<=endprivate) return ICOSaleState.PrivateSale;
       if(now>endprivate && now<=endpresale) return ICOSaleState.PreSale;
       if(now>endpresale && now<=token.fundingEndBlock()) return ICOSaleState.PublicSale;
       if(now>token.fundingEndBlock() && token.usdraised()<token.minCapUSD()) return ICOSaleState.Failed;
       if(now>token.fundingEndBlock() && token.usdraised()>=token.minCapUSD()) return ICOSaleState.Success;
    }

    

}
///////https://ethereum.stackexchange.com/questions/11383/oracle-oraclize-it-with-truffle-and-testrpc
////https://ethereum.stackexchange.com/questions/17015/regarding-oraclize-call-in-smart-contract
function changeEndPrivateSale(uint256 _newend) public onlyOwner{
    endprivate = _newend;
}

///change the end private sale