contract CryptojoyToken is pairToken {
    using SafeMath for uint;

    string public name = "cryptojoy token";                    
    string public symbol = "CJT";                
    uint public totalSupply = 10**10 * 10**18; // 1 billion
    uint public miningSupply; // minable part

    uint constant MAGNITUDE = 10**6;
    uint constant LOG1DOT5 = 405465; // log(1.5) under MAGNITUDE
    uint constant THREE_SECOND= 15 * MAGNITUDE / 10; // 1.5 under MAGNITUDE
    uint constant MINING_INTERVAL = 365; // number of windows that the price is fixed

    uint public a; // paremeter a of the price fuction price = a*log(t)+b, 18 decimals
    uint public b; // paremeter b of the price fuction price = a*log(t)+b, 18 decimals
    uint public blockInterval; // number of blocks where the token price is fixed
    uint public startBlockNumber; // The starting block that the token can be mint.

    address public platform;
    uint public lowerBoundaryETH; // Refuse incoming ETH lower than this value
    uint public upperBoundaryETH; // Refuse incoming ETH higher than this value

    uint public supplyPerInterval; // miningSupply / MINING_INTERVAL
    uint public tokenMint = 0;

    bool paraInitialized = false;

    /// @param _beneficiary Address to send the remaining tokens
    /// @param _miningSupply Amount of tokens of mining
    constructor(
        address _beneficiary, 
        uint _miningSupply)
        public {
        require(_miningSupply < totalSupply, "Insufficient total supply");
        miningSupply = _miningSupply;
        uint _amount = totalSupply.sub(_miningSupply);
        balances[address(this)] = miningSupply;
        balances[_beneficiary] = _amount;
        supplyPerInterval = miningSupply / MINING_INTERVAL;
    }


    /// @dev sets boundaries for incoming tx
    /// @dev from FoMo3Dlong
    modifier isWithinLimits(uint _eth) {
        require(_eth >= lowerBoundaryETH, "pocket lint: not a valid currency");
        require(_eth <= upperBoundaryETH, "no vitalik, no");
        _;
    }

    /// @dev Initialize the token mint parameters
    /// @dev Can only be excuted once.
    function initPara(
        uint _a, 
        uint _b, 
        uint _blockInterval, 
        uint _startBlockNumber,
        address _platform,
        uint _lowerBoundaryETH,
        uint _upperBoundaryETH) 
        public 
        onlyOwner {
        require(!paraInitialized, "Parameters are already set");
        require(_lowerBoundaryETH < _upperBoundaryETH, "Lower boundary is larger than upper boundary!");
        a = _a;
        b = _b;
        blockInterval = _blockInterval;
        startBlockNumber = _startBlockNumber;

        platform = _platform;
        lowerBoundaryETH = _lowerBoundaryETH;
        upperBoundaryETH = _upperBoundaryETH;

        paraInitialized = true;
    }

    function changeWithdraw(address _platform) public onlyOwner {
        platform = _platform;
    }

    /// @dev Mint token based on the current token price.
    /// @dev The token number is limited during each interval.
    function buy() public isWithinLimits(msg.value) payable {
        uint currentStage = getCurrentStage(); // from 1 to MINING_INTERVAL
        require(tokenMint < currentStage.mul(supplyPerInterval), "No token avaiable");
        uint currentPrice = calculatePrice(currentStage); // 18 decimal
        uint amountToBuy = msg.value.mul(10**uint(decimals)).div(currentPrice);
        
        if(tokenMint.add(amountToBuy) > currentStage.mul(supplyPerInterval)) {
            amountToBuy = currentStage.mul(supplyPerInterval).sub(tokenMint);
            balances[address(this)] = balances[address(this)].sub(amountToBuy);
            balances[msg.sender] = balances[msg.sender].add(amountToBuy);
            tokenMint = tokenMint.add(amountToBuy);
            uint refund = msg.value.sub(amountToBuy.mul(currentPrice).div(10**uint(decimals)));
            msg.sender.transfer(refund);          
            platform.transfer(msg.value.sub(refund)); 
        } else {
            balances[address(this)] = balances[address(this)].sub(amountToBuy);
            balances[msg.sender] = balances[msg.sender].add(amountToBuy);
            tokenMint = tokenMint.add(amountToBuy);
            platform.transfer(msg.value);
        }
        emit Buy(msg.sender, amountToBuy);
    }

    function() public payable {
        buy();
    }

    /// @dev Shows the remaining token of the current token mint phase
    function tokenRemain() public view returns (uint) {
        uint currentStage = getCurrentStage();
        return currentStage * supplyPerInterval - tokenMint;
    }

    /// @dev Get the current token mint phase between 1 and MINING_INTERVAL
    function getCurrentStage() public view returns (uint) {
        require(block.number >= startBlockNumber, "Not started yet");
        uint currentStage = (block.number.sub(startBlockNumber)).div(blockInterval) + 1;
        if (currentStage <= MINING_INTERVAL) {
            return currentStage;
        } else {
            return MINING_INTERVAL;
        }
    }

    /// @dev Return the price of one token during the nth stage
    /// @param stage Current stage from 1 to 365
    /// @return Price per token
    function calculatePrice(uint stage) public view returns (uint) {
        return a.mul(log(stage.mul(MAGNITUDE))).div(MAGNITUDE).add(b);
    }

    /// @dev Return the e based logarithm of x demonstrated by Vitalik
    /// @param input The actual input (>=1) times MAGNITUDE
    /// @return result The actual output times MAGNITUDE
    function log(uint input) internal pure returns (uint) {
        uint x = input;
        require(x >= MAGNITUDE);
        if (x == MAGNITUDE) {
            return 0;
        }
        uint result = 0;
        while (x >= THREE_SECOND) {
            result += LOG1DOT5;
            x = x * 2 / 3;
        }
        
        x = x - MAGNITUDE;
        uint y = x;
        uint i = 1;
        while (i < 10) {
            result = result + (y / i);
            i += 1;
            y = y * x / MAGNITUDE;
            result = result - (y / i);
            i += 1;
            y = y * x / MAGNITUDE;
        }
        
        return result;
    }

    event Buy(address indexed _buyer, uint _value);
}
/// @dev A pair token that can be mint by sending ether into this contract.
/// @dev The price of the token follows price = a*log(t)+b, where a and b are
/// @dev two parameters to be set, and t is the current round depends on current
/// @dev block height.
function initPara(
    uint _a, 
    uint _b, 
    uint _blockInterval, 
    uint _startBlockNumber,
    address _platform,
    uint _lowerBoundaryETH,
    uint _upperBoundaryETH) 
    public 
    onlyOwner {
    require(!paraInitialized, "Parameters are already set");
    require(_lowerBoundaryETH < _upperBoundaryETH, "Lower boundary is larger than upper boundary!");
    a = _a;
    b = _b;
    blockInterval = _blockInterval;
    startBlockNumber = _startBlockNumber;

    platform = _platform;
    lowerBoundaryETH = _lowerBoundaryETH;
    upperBoundaryETH = _upperBoundaryETH;

    paraInitialized = true;
}

/// @dev Initialize the token mint parameters
/// @dev Can only be excuted once.