pragma solidity ^0.4.23;

library SafeMathLib {

    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a * b;
        assert(a == 0 || c / a == b);
        return c;
    }

    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        assert(b > 0);
        uint256 c = a / b;
        assert(a == b * c + a % b);
        return c;
    }

    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        assert(b <= a);
        return a - b;
    }

    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        assert(c >= a);
        return c;
    }
}

contract DateTimeLib {

    struct _DateTime {
        uint16 year;
        uint8 month;
        uint8 day;
        uint8 hour;
        uint8 minute;
        uint8 second;
        uint8 weekday;
    }

    uint constant DAY_IN_SECONDS = 86400;
    uint constant YEAR_IN_SECONDS = 31536000;
    uint constant LEAP_YEAR_IN_SECONDS = 31622400;

    uint constant HOUR_IN_SECONDS = 3600;
    uint constant MINUTE_IN_SECONDS = 60;

    uint16 constant ORIGIN_YEAR = 1970;

    function isLeapYear(uint16 year) internal pure returns (bool) {
        if (year % 4 != 0) {
            return false;
        }
        if (year % 100 != 0) {
            return true;
        }
        if (year % 400 != 0) {
            return false;
        }
        return true;
    }

    function leapYearsBefore(uint year) internal pure returns (uint) {
        year -= 1;
        return year / 4 - year / 100 + year / 400;
    }

    function getDaysInMonth(uint8 month, uint16 year) internal pure returns (uint8) {
        if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) {
            return 31;
        }
        else if (month == 4 || month == 6 || month == 9 || month == 11) {
            return 30;
        }
        else if (isLeapYear(year)) {
            return 29;
        }
        else {
            return 28;
        }
    }

    function parseTimestamp(uint timestamp) internal pure returns (_DateTime dt) {
        uint secondsAccountedFor = 0;
        uint buf;
        uint8 i;

        dt.year = getYear(timestamp);
        buf = leapYearsBefore(dt.year) - leapYearsBefore(ORIGIN_YEAR);
        secondsAccountedFor += LEAP_YEAR_IN_SECONDS * buf;
        secondsAccountedFor += YEAR_IN_SECONDS * (dt.year - ORIGIN_YEAR - buf);

        uint secondsInMonth;
        for (i = 1; i <= 12; i++) {
            secondsInMonth = DAY_IN_SECONDS * getDaysInMonth(i, dt.year);
            if (secondsInMonth + secondsAccountedFor > timestamp) {
                dt.month = i;
                break;
            }
            secondsAccountedFor += secondsInMonth;
        }

        for (i = 1; i <= getDaysInMonth(dt.month, dt.year); i++) {
            if (DAY_IN_SECONDS + secondsAccountedFor > timestamp) {
                dt.day = i;
                break;
            }
            secondsAccountedFor += DAY_IN_SECONDS;
        }
        dt.hour = getHour(timestamp);
        dt.minute = getMinute(timestamp);
        dt.second = getSecond(timestamp);
        dt.weekday = getWeekday(timestamp);
    }

    function getYear(uint timestamp) internal pure returns (uint16) {
        uint secondsAccountedFor = 0;
        uint16 year;
        uint numLeapYears;

        year = uint16(ORIGIN_YEAR + timestamp / YEAR_IN_SECONDS);
        numLeapYears = leapYearsBefore(year) - leapYearsBefore(ORIGIN_YEAR);

        secondsAccountedFor += LEAP_YEAR_IN_SECONDS * numLeapYears;
        secondsAccountedFor += YEAR_IN_SECONDS * (year - ORIGIN_YEAR - numLeapYears);

        while (secondsAccountedFor > timestamp) {
            if (isLeapYear(uint16(year - 1))) {
                secondsAccountedFor -= LEAP_YEAR_IN_SECONDS;
            }
            else {
                secondsAccountedFor -= YEAR_IN_SECONDS;
            }
            year -= 1;
        }
        return year;
    }

    function getMonth(uint timestamp) internal pure returns (uint8) {
        return parseTimestamp(timestamp).month;
    }

    function getDay(uint timestamp) internal pure returns (uint8) {
        return parseTimestamp(timestamp).day;
    }

    function getHour(uint timestamp) internal pure returns (uint8) {
        return uint8((timestamp / 60 / 60) % 24);
    }

    function getMinute(uint timestamp) internal pure returns (uint8) {
        return uint8((timestamp / 60) % 60);
    }

    function getSecond(uint timestamp) internal pure returns (uint8) {
        return uint8(timestamp % 60);
    }

    function getWeekday(uint timestamp) internal pure returns (uint8) {
        return uint8((timestamp / DAY_IN_SECONDS + 4) % 7);
    }

    function toTimestamp(uint16 year, uint8 month, uint8 day) internal pure returns (uint timestamp) {
        return toTimestamp(year, month, day, 0, 0, 0);
    }

    function toTimestamp(uint16 year, uint8 month, uint8 day, uint8 hour) internal pure returns (uint timestamp) {
        return toTimestamp(year, month, day, hour, 0, 0);
    }

    function toTimestamp(uint16 year, uint8 month, uint8 day, uint8 hour, uint8 minute) internal pure returns (uint timestamp) {
        return toTimestamp(year, month, day, hour, minute, 0);
    }

    function toTimestamp(uint16 year, uint8 month, uint8 day, uint8 hour, uint8 minute, uint8 second) internal pure returns (uint timestamp) {
        uint16 i;
        for (i = ORIGIN_YEAR; i < year; i++) {
            if (isLeapYear(i)) {
                timestamp += LEAP_YEAR_IN_SECONDS;
            }
            else {
                timestamp += YEAR_IN_SECONDS;
            }
        }

        uint8[12] memory monthDayCounts;
        monthDayCounts[0] = 31;
        if (isLeapYear(year)) {
            monthDayCounts[1] = 29;
        }
        else {
            monthDayCounts[1] = 28;
        }
        monthDayCounts[2] = 31;
        monthDayCounts[3] = 30;
        monthDayCounts[4] = 31;
        monthDayCounts[5] = 30;
        monthDayCounts[6] = 31;
        monthDayCounts[7] = 31;
        monthDayCounts[8] = 30;
        monthDayCounts[9] = 31;
        monthDayCounts[10] = 30;
        monthDayCounts[11] = 31;

        for (i = 1; i < month; i++) {
            timestamp += DAY_IN_SECONDS * monthDayCounts[i - 1];
        }

        timestamp += DAY_IN_SECONDS * (day - 1);
        timestamp += HOUR_IN_SECONDS * (hour);
        timestamp += MINUTE_IN_SECONDS * (minute);
        timestamp += second;

        return timestamp;
    }
}

interface IERC20 {
    
    function totalSupply() external constant returns (uint256);
    function balanceOf(address _owner) external constant returns (uint256 balance);
    function transfer(address _to, uint256 _value) external returns (bool success);
    function transferFrom(address _from, address _to, uint256 _value) external returns (bool success);
    function approve(address _spender, uint256 _value) external returns (bool success);
    function allowance(address _owner, address _spender) external constant returns (uint256 remaining);

    event Transfer(address indexed _from, address indexed _to, uint256 _value);
    event Approval(address indexed _owner, address _spender, uint256 _value);
}

contract StandardToken is IERC20,DateTimeLib {

    using SafeMathLib for uint256;

    mapping(address => uint256) balances;

    mapping(address => mapping(address => uint256)) allowed;
    
    string public constant symbol = "JCB";
    
    string public constant name = "JoinCoin";
    
    uint _totalSupply = 1000000000 * 10 ** 8;
    
    uint8 public constant decimals = 8;
    
    function totalSupply() external constant returns (uint256) {
        return _totalSupply;
    }
    
    function balanceOf(address _owner) external constant returns (uint256 balance) {
        return balances[_owner];
    }
    
    function transfer(address _to, uint256 _value) public returns (bool success) {
        return transferInternal(msg.sender, _to, _value);
    }

    function transferInternal(address _from, address _to, uint256 _value) internal returns (bool success) {
        require(_value > 0 && balances[_from] >= _value);
        balances[_from] = balances[_from].sub(_value);
        balances[_to] = balances[_to].add(_value);
        emit Transfer(_from, _to, _value);
        return true;
    }

    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
        require(_value > 0 && allowed[_from][msg.sender] >= _value && balances[_from] >= _value);
        balances[_from] = balances[_from].sub(_value);
        balances[_to] = balances[_to].add(_value);
        allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
        emit Transfer(_from, _to, _value);
        return true;
    }

    function approve(address _spender, uint256 _value) public returns (bool success) {
        allowed[msg.sender][_spender] = _value;
        emit Approval(msg.sender, _spender, _value);
        return true;
    }

    function allowance(address _owner, address _spender) public constant returns (uint256 remaining) {
        return allowed[_owner][_spender];
    }
}

contract LockableToken is StandardToken {
    
    address internal developerReservedAddress = 0xf4ab0e0269E05163E65608F44E80A125A511a916;
    
    uint[4] internal developerReservedUnlockTimes;
    
    uint256[4] internal developerReservedBalanceLimits;
    
    function getDeveloperReservedBalanceLimit() internal returns (uint256 balanceLimit) {
        uint time = now;
        for (uint index = 0; index < developerReservedUnlockTimes.length; index++) {
            if (developerReservedUnlockTimes[index] == 0x0) {
                continue;
            }
            if (time > developerReservedUnlockTimes[index]) {
                developerReservedUnlockTimes[index] = 0x0;
            } else {
                return developerReservedBalanceLimits[index];
            }
        }
        return 0;
    }
    
    function transfer(address _to, uint256 _value) public returns (bool success) {
        return transferInternal(msg.sender, _to, _value);
    }

    function transferInternal(address _from, address _to, uint256 _value) internal returns (bool success) {
        require(_from != 0x0 && _to != 0x0 && _value > 0x0);
        if (_from == developerReservedAddress) {
            uint256 balanceLimit = getDeveloperReservedBalanceLimit();
            require(balances[_from].sub(balanceLimit) >= _value);
        }
        return super.transferInternal(_from, _to, _value);
    }
    
    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
        require(_from != 0x0 && _to != 0x0 && _value > 0x0);
        if (_from == developerReservedAddress) {
            uint256 balanceLimit = getDeveloperReservedBalanceLimit();
            require(balances[_from].sub(balanceLimit) >= _value);
        }
        return super.transferFrom(_from, _to, _value);
    }
    
    event UnlockTimeChanged(uint index, uint unlockTime, uint newUnlockTime);
    event LockInfo(address indexed publicOfferingAddress, uint index, uint unlockTime, uint256 balanceLimit);
}

contract TradeableToken is LockableToken {

    address internal publicOfferingAddress = 0xbdB45923Bf511801F71EAeD4fbd926E48bA43DB5;

    uint256 public exchangeRate = 4000;

    function buy(address _beneficiary, uint256 _weiAmount) internal {
        require(_beneficiary != 0x0);
        require(publicOfferingAddress != 0x0);
        require(exchangeRate > 0x0);
        require(_weiAmount > 0x0);

        uint256 exchangeToken = _weiAmount.mul(exchangeRate);
        exchangeToken = exchangeToken.div(1 * 10 ** 10);

        publicOfferingAddress.transfer(_weiAmount);
        super.transferInternal(publicOfferingAddress, _beneficiary, exchangeToken);
    }
    
    event ExchangeRateChanged(uint256 oldExchangeRate,uint256 newExchangeRate);
}

contract OwnableToken is TradeableToken {
    
    address internal owner = 0xfe71ea94aD48c03a8B1D5c8A371E36Fc0E05856A;
    
    mapping(address => uint) administrators;
    
    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }
    
    modifier onlyAdministrator() {
        require(msg.sender == owner || administrators[msg.sender] > 0x0);
        _;
    }
    
    function transferOwnership(address _newOwner) onlyOwner public {
        require(_newOwner != address(0));
        owner = _newOwner;
        emit OwnershipTransferred(owner, _newOwner);
    }
    
    function addAdministrator(address _adminAddress) onlyOwner public {
        require(_adminAddress != address(0));
        require(administrators[_adminAddress] <= 0x0);
        administrators[_adminAddress] = 0x1;
        emit AddAdministrator(_adminAddress);
    }
    
    function removeAdministrator(address _adminAddress) onlyOwner public {
        require(_adminAddress != address(0));
        require(administrators[_adminAddress] > 0x0);
        administrators[_adminAddress] = 0x0;
        emit RemoveAdministrator(_adminAddress);
    }
    
    function setExchangeRate(uint256 _exchangeRate) public onlyAdministrator returns (bool success) {
        require(_exchangeRate > 0x0);
        uint256 oldExchangeRate = exchangeRate;
        exchangeRate = _exchangeRate;
        emit ExchangeRateChanged(oldExchangeRate, exchangeRate);
        return true;
    }
    
    function changeUnlockTime(uint _index, uint _unlockTime) public onlyAdministrator returns (bool success) {
        require(_index >= 0x0 && _index < developerReservedUnlockTimes.length && _unlockTime > 0x0);
        if(_index > 0x0) {
            uint beforeUnlockTime = developerReservedUnlockTimes[_index - 1];
            require(beforeUnlockTime == 0x0 || beforeUnlockTime < _unlockTime);
        }
        if(_index < developerReservedUnlockTimes.length - 1) {
            uint afterUnlockTime = developerReservedUnlockTimes[_index + 1];
            require(afterUnlockTime == 0x0 || _unlockTime < afterUnlockTime);
        }
        uint oldUnlockTime = developerReservedUnlockTimes[_index];
        developerReservedUnlockTimes[_index] = _unlockTime;
        emit UnlockTimeChanged(_index,oldUnlockTime,_unlockTime);
        return true;
    }
    
    function getDeveloperReservedLockInfo(uint _index) public onlyAdministrator returns (uint, uint256) {
        require(_index >= 0x0 && _index < developerReservedUnlockTimes.length && _index < developerReservedBalanceLimits.length);
        emit LockInfo(developerReservedAddress,_index,developerReservedUnlockTimes[_index],developerReservedBalanceLimits[_index]);
        return (developerReservedUnlockTimes[_index], developerReservedBalanceLimits[_index]);
    }
    
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    event AddAdministrator(address indexed adminAddress);
    event RemoveAdministrator(address indexed adminAddress);
}

contract JCB is OwnableToken {
    
    function JCB() public {
        balances[owner] = 500000000 * 10 ** 8;
        balances[publicOfferingAddress] = 300000000 * 10 ** 8;

        uint256 developerReservedBalance = 200000000 * 10 ** 8;
        balances[developerReservedAddress] = developerReservedBalance;
        developerReservedUnlockTimes =
        [
        DateTimeLib.toTimestamp(2019, 7, 1),
        DateTimeLib.toTimestamp(2020, 7, 1),
        DateTimeLib.toTimestamp(2021, 7, 1),
        DateTimeLib.toTimestamp(2022, 7, 1)
        ];
        developerReservedBalanceLimits = 
        [
            developerReservedBalance,
            developerReservedBalance - (developerReservedBalance / 4) * 1,
            developerReservedBalance - (developerReservedBalance / 4) * 2,
            developerReservedBalance - (developerReservedBalance / 4) * 3
        ];
    }
    
    function() public payable {
        buy(msg.sender, msg.value);
    }
}