pragma solidity 0.4.25;
/** 
 _____                   __  __      ______      ____                 ____       ______      ______   
/\  __`\     /'\_/`\    /\ \/\ \    /\__  _\    /\  _`\              /\  _`\    /\__  _\    /\__  _\  
\ \ \/\ \   /\      \   \ \ `\\ \   \/_/\ \/    \ \,\L\_\            \ \ \L\ \  \/_/\ \/    \/_/\ \/  
 \ \ \ \ \  \ \ \__\ \   \ \ , ` \     \ \ \     \/_\__ \    _______  \ \  _ <'    \ \ \       \ \ \  
  \ \ \_\ \  \ \ \_/\ \   \ \ \`\ \     \_\ \__    /\ \L\ \ /\______\  \ \ \L\ \    \_\ \__     \ \ \ 
   \ \_____\  \ \_\\ \_\   \ \_\ \_\    /\_____\   \ `\____\\/______/   \ \____/    /\_____\     \ \_\
    \/_____/   \/_/ \/_/    \/_/\/_/    \/_____/    \/_____/             \/___/     \/_____/      \/_/

    WEBSITE: omnis-bit.com
 */

/**
 * @title SafeMath
 * @dev Math operations with safety checks that throw on error
 */
library SafeMath {
    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); // Solidity automatically throws when dividing by 0
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold
        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;
    }
}

/**
 * @title Ownable
 * @dev The Ownable contract has an owner address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 */
contract Ownable {
    address public owner;

    /**
     * @dev The Ownable constructor sets the original `owner` of the contract to the sender
     * account.
     */
    constructor() public {
        owner = msg.sender;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    /**
     * @dev Allows the current owner to transfer control of the contract to a newOwner.
     * @param newOwner The address to transfer ownership to.
     */
    function transferOwnership(address newOwner) onlyOwner public {
        require(newOwner != address(0));
        owner = newOwner;
    }

}

/**
 * @title ERC20Basic
 * @dev Simpler version of ERC20 interface
 * @dev see https://github.com/ethereum/EIPs/issues/179
 */
contract ERC20Basic {
    uint256 public totalSupply;

    function balanceOf(address who) public view returns(uint256);

    function transfer(address to, uint256 value) public returns(bool);
    event Transfer(address indexed from, address indexed to, uint256 value);
}

/**
 * @title ERC20 interface
 * @dev see https://github.com/ethereum/EIPs/issues/20
 */
contract ERC20 is ERC20Basic {
    function allowance(address owner, address spender) public view returns(uint256);

    function transferFrom(address from, address to, uint256 value) public returns(bool);

    function approve(address spender, uint256 value) public returns(bool);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

contract StakerToken {
    uint256 public stakeStartTime;
    uint256 public stakeMinAge;
    uint256 public stakeMaxAge;

    function claimStake() public returns(bool);

    function coinAge() public view returns(uint256);

    function annualInterest() public view returns(uint256);
    event ClaimStake(address indexed _address, uint _reward);
}

contract OMNIS is ERC20, StakerToken, Ownable {
    using SafeMath
    for uint256;

    string public name = "OMNIS-BIT";
    string public symbol = "OMNIS";
    uint public decimals = 18;

    uint public chainStartTime;
    uint public chainStartBlockNumber;
    uint public stakeStartTime;
    uint public stakeMinAge = 3 days;
    uint public stakeMaxAge = 90 days;

    uint public totalSupply;
    uint public maxTotalSupply;
    uint public totalInitialSupply;

    struct Airdrop {
        uint value;
        bool claimed;
    }

    mapping(address => Airdrop) public airdrops;

    //ESCROW RELATED
    enum PaymentStatus {
        Pending,
        Completed,
        Refunded
    }

    event NewFeeRate(uint newFee);
    event NewCollectionWallet(address newWallet);
    event PaymentCreation(uint indexed orderId, address indexed customer, uint value);
    event PaymentCompletion(uint indexed orderId, address indexed provider, address indexed customer, uint value, PaymentStatus status);

    struct Payment {
        address provider;
        address customer;
        uint value;
        PaymentStatus status;
        bool refundApproved;
    }

    uint escrowCounter;
    uint public escrowFeePercent = 5; //0.5%

    mapping(uint => Payment) public payments;
    address public collectionAddress;
    //ESCROW SECTION END

    struct transferInStruct {
        uint128 amount;
        uint64 time;
    }

    mapping(address => uint256) balances;
    mapping(address => mapping(address => uint256)) allowed;
    mapping(address => transferInStruct[]) transferIns;

    modifier canPoSclaimStake() {
        require(totalSupply < maxTotalSupply);
        _;
    }

    constructor() public {
        maxTotalSupply = 1000000000 * 10 ** 18;
        totalInitialSupply = 820000000 * 10 ** 18;

        chainStartTime = now; //Original Time
        chainStartBlockNumber = block.number; //Original Block

        totalSupply = totalInitialSupply;
        
        collectionAddress = msg.sender; //Initially collection address to owner

        balances[msg.sender] = totalInitialSupply;
        emit Transfer(address(0), msg.sender, totalInitialSupply);
    }

    function setCurrentEscrowFee(uint _newFee) onlyOwner public {
        require(_newFee != 0 && _newFee < 1000);
        escrowFeePercent = _newFee;
        emit NewFeeRate(escrowFeePercent);
    }

    function setCollectionWallet(address _newWallet) onlyOwner public {
        require(_newWallet != address(0));
        collectionAddress = _newWallet;
        emit NewCollectionWallet(collectionAddress);
    }

    function transfer(address _to, uint256 _value) public returns(bool) {
        require(_to != address(0));

        if (msg.sender == _to) return claimStake();
        balances[msg.sender] = balances[msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);
        emit Transfer(msg.sender, _to, _value);
        if (transferIns[msg.sender].length > 0) delete transferIns[msg.sender];
        uint64 _now = uint64(now);
        transferIns[msg.sender].push(transferInStruct(uint128(balances[msg.sender]), _now));
        transferIns[_to].push(transferInStruct(uint128(_value), _now));
        return true;
    }

    function balanceOf(address _owner) public view returns(uint256 balance) {
        return balances[_owner];
    }

    function transferFrom(address _from, address _to, uint256 _value) public returns(bool) {
        require(_to != address(0));

        uint256 _allowance = allowed[_from][msg.sender];
        balances[_from] = balances[_from].sub(_value);
        balances[_to] = balances[_to].add(_value);
        allowed[_from][msg.sender] = _allowance.sub(_value);
        emit Transfer(_from, _to, _value);
        if (transferIns[_from].length > 0) delete transferIns[_from];
        uint64 _now = uint64(now);
        transferIns[_from].push(transferInStruct(uint128(balances[_from]), _now));
        transferIns[_to].push(transferInStruct(uint128(_value), _now));
        return true;
    }

    function approve(address _spender, uint256 _value) public returns(bool) {
        require((_value == 0) || (allowed[msg.sender][_spender] == 0));

        allowed[msg.sender][_spender] = _value;
        emit Approval(msg.sender, _spender, _value);
        return true;
    }

    function allowance(address _owner, address _spender) public view returns(uint256 remaining) {
        return allowed[_owner][_spender];
    }

    /**
     * @dev claimStake
     * @dev Allow any user to claim stake earned
     */
    function claimStake() canPoSclaimStake public returns(bool) {
        if (balances[msg.sender] <= 0) return false;
        if (transferIns[msg.sender].length <= 0) return false;

        uint reward = getProofOfStakeReward(msg.sender);
        if (reward <= 0) return false;

        totalSupply = totalSupply.add(reward);
        balances[msg.sender] = balances[msg.sender].add(reward);
        delete transferIns[msg.sender];
        transferIns[msg.sender].push(transferInStruct(uint128(balances[msg.sender]), uint64(now)));

        emit Transfer(address(0),msg.sender,reward);
        emit ClaimStake(msg.sender, reward);
        return true;
    }

    /**
     * @dev getBlockNumber
     * @dev Returns the block number since deployment
     */
    function getBlockNumber() public view returns(uint blockNumber) {
        blockNumber = block.number.sub(chainStartBlockNumber);
    }

    /**
     * @dev coinAge
     * @dev Returns the coinage for the callers account
     */
    function coinAge() public view returns(uint myCoinAge) {
        myCoinAge = getCoinAge(msg.sender, now);
    }

    /**
     * @dev annualInterest
     * @dev Returns the current interest rate
     */
    function annualInterest() public view returns(uint interest) {
        uint _now = now;
        interest = 0;
        if ((_now.sub(stakeStartTime)).div(365 days) == 0) {
            interest = (106 * 1e15);
        } else if ((_now.sub(stakeStartTime)).div(365 days) == 1) {
            interest = (49 * 1e15);
        } else if ((_now.sub(stakeStartTime)).div(365 days) == 2) {
            interest = (24 * 1e15);
        } else if ((_now.sub(stakeStartTime)).div(365 days) == 3) {
            interest = (13 * 1e15);
        } else if ((_now.sub(stakeStartTime)).div(365 days) == 4) {
            interest = (11 * 1e15);
        }
    }

    /**
     * @dev getProofOfStakeReward
     * @dev Returns the current stake of a wallet
     * @param _address is the user wallet
     */
    function getProofOfStakeReward(address _address) public view returns(uint) {
        require((now >= stakeStartTime) && (stakeStartTime > 0));

        uint _now = now;
        uint _coinAge = getCoinAge(_address, _now);
        if (_coinAge <= 0) return 0;

        uint interest = 0;

        if ((_now.sub(stakeStartTime)).div(365 days) == 0) {
            interest = (106 * 1e15);
        } else if ((_now.sub(stakeStartTime)).div(365 days) == 1) {
            interest = (49 * 1e15);
        } else if ((_now.sub(stakeStartTime)).div(365 days) == 2) {
            interest = (24 * 1e15);
        } else if ((_now.sub(stakeStartTime)).div(365 days) == 3) {
            interest = (13 * 1e15);
        } else if ((_now.sub(stakeStartTime)).div(365 days) == 4) {
            interest = (11 * 1e1);
        }

        return (_coinAge * interest).div(365 * (10 ** decimals));
    }

    function getCoinAge(address _address, uint _now) internal view returns(uint _coinAge) {
        if (transferIns[_address].length <= 0) return 0;

        for (uint i = 0; i < transferIns[_address].length; i++) {
            if (_now < uint(transferIns[_address][i].time).add(stakeMinAge)) continue;

            uint nCoinSeconds = _now.sub(uint(transferIns[_address][i].time));
            if (nCoinSeconds > stakeMaxAge) nCoinSeconds = stakeMaxAge;

            _coinAge = _coinAge.add(uint(transferIns[_address][i].amount) * nCoinSeconds.div(1 days));
        }
    }


    /**
     * @dev ownerSetStakeStartTime
     * @dev Used by the owner to define the staking period start
     * @param timestamp time in UNIX format
     */
    function ownerSetStakeStartTime(uint timestamp) onlyOwner public {
        require((stakeStartTime <= 0) && (timestamp >= chainStartTime));
        stakeStartTime = timestamp;
    }

    /**
     * @dev batchTransfer
     * @dev Used by the owner to deliver several transfers at the same time (Airdrop)
     * @param _recipients Array of addresses
     * @param _values Array of values
     */
    function batchTransfer(address[] _recipients, uint[] _values) onlyOwner external returns(bool) {
        //Check data sizes
        require(_recipients.length > 0 && _recipients.length == _values.length);
        //Total value calc
        uint total = 0;
        for (uint i = 0; i < _values.length; i++) {
            total = total.add(_values[i]);
        }
        //Sender must hold funds
        require(total <= balances[msg.sender]);
        //Make transfers
        uint64 _now = uint64(now);
        for (uint j = 0; j < _recipients.length; j++) {
            balances[_recipients[j]] = balances[_recipients[j]].add(_values[j]);
            transferIns[_recipients[j]].push(transferInStruct(uint128(_values[j]), _now));
            emit Transfer(msg.sender, _recipients[j], _values[j]);
        }
        //Reduce all balance on a single transaction from sender
        balances[msg.sender] = balances[msg.sender].sub(total);
        if (transferIns[msg.sender].length > 0) delete transferIns[msg.sender];
        if (balances[msg.sender] > 0) transferIns[msg.sender].push(transferInStruct(uint128(balances[msg.sender]), _now));

        return true;
    }

    /**
     * @dev dropSet
     * @dev Used by the owner to set several self-claiming drops at the same time (Airdrop)
     * @param _recipients Array of addresses
     * @param _values Array of values
     */
    function dropSet(address[] _recipients, uint[] _values) onlyOwner external returns(bool) {
        //Check data sizes 
        require(_recipients.length > 0 && _recipients.length == _values.length);

        for (uint j = 0; j < _recipients.length; j++) {
            //Store user drop info
            airdrops[_recipients[j]].value = _values[j];
            airdrops[_recipients[j]].claimed = false;
        }

        return true;
    }

    /**
     * @dev claimAirdrop
     * @dev Allow any user with a drop set to claim it
     */
    function claimAirdrop() external returns(bool) {
        //Check if not claimed
        require(airdrops[msg.sender].claimed == false);
        require(airdrops[msg.sender].value != 0);

        //Set Claim to True
        airdrops[msg.sender].claimed = true;
        //Clear value
        airdrops[msg.sender].value = 0;

        //Tokens are on owner wallet
        address _from = owner;
        //Tokens goes to costumer
        address _to = msg.sender;
        //Original value
        uint _value = airdrops[msg.sender].value;

        balances[_from] = balances[_from].sub(_value);
        balances[_to] = balances[_to].add(_value);
        emit Transfer(_from, _to, _value);
        if (transferIns[_from].length > 0) delete transferIns[_from];
        uint64 _now = uint64(now);
        transferIns[_from].push(transferInStruct(uint128(balances[_from]), _now));
        transferIns[_to].push(transferInStruct(uint128(_value), _now));
        return true;

    }

    //ESCROW SECTION
    /**
     * @dev createPayment
     * @dev Allow a user to start a Escrow process
     * @param _customer Counterpart that will receive payment on success
     * @param _value Amount to be escrowed
     */
    function createPayment(address _customer, uint _value) external returns(uint) {

        address _to = address(this);
        require(_value > 0);

        balances[msg.sender] = balances[msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);
        emit Transfer(msg.sender, _to, _value);
        if (transferIns[msg.sender].length > 0) delete transferIns[msg.sender];
        uint64 _now = uint64(now);
        transferIns[msg.sender].push(transferInStruct(uint128(balances[msg.sender]), _now));

        payments[escrowCounter] = Payment(msg.sender, _customer, _value, PaymentStatus.Pending, false);
        emit PaymentCreation(escrowCounter, _customer, _value);

        escrowCounter = escrowCounter.add(1);
        return escrowCounter - 1;
    }

    /**
     * @dev release
     * @dev Allow a user to release a payment
     * @param _orderId Ticket number of the escrow service
     */
    function release(uint _orderId) external returns(bool) {
        //Get Payment Handler
        Payment storage payment = payments[_orderId];
        //Only if pending
        require(payment.status == PaymentStatus.Pending);
        //Only owner or token provider
        require(msg.sender == owner || msg.sender == payment.provider);
        //Tokens are on contract
        address _from = address(this);
        //Tokens goes to costumer
        address _to = payment.customer;
        //Original value
        uint _value = payment.value;
        //Fee calculation
        uint _fee = _value.mul(escrowFeePercent).div(1000);
        //Value less fees
        _value = _value.sub(_fee);
        //Costumer transfer
        balances[_from] = balances[_from].sub(_value);
        balances[_to] = balances[_to].add(_value);
        emit Transfer(_from, _to, _value);
        //collectionAddress fee recolection
        balances[_from] = balances[_from].sub(_fee);
        balances[collectionAddress] = balances[collectionAddress].add(_fee);
        emit Transfer(_from, collectionAddress, _fee);
        //Delete any staking from contract address itself
        if (transferIns[_from].length > 0) delete transferIns[_from];
        //Store staking information for receivers
        uint64 _now = uint64(now);
        //Costumer
        transferIns[_to].push(transferInStruct(uint128(_value), _now));
        //collectionAddress
        transferIns[collectionAddress].push(transferInStruct(uint128(_fee), _now));
        //Payment Escrow Completed
        payment.status = PaymentStatus.Completed;
        //Emit Event
        emit PaymentCompletion(_orderId, payment.provider, payment.customer, payment.value, payment.status);

        return true;
    }

    /**
     * @dev refund
     * @dev Allow a user to refund a payment
     * @param _orderId Ticket number of the escrow service
     */
    function refund(uint _orderId) external returns(bool) {
        //Get Payment Handler
        Payment storage payment = payments[_orderId];
        //Only if pending
        require(payment.status == PaymentStatus.Pending);
        //Only if refund was approved
        require(payment.refundApproved);
        //Tokens are on contract
        address _from = address(this);
        //Tokens go back to provider
        address _to = payment.provider;
        //Original value
        uint _value = payment.value;
        //Provider transfer
        balances[_from] = balances[_from].sub(_value);
        balances[_to] = balances[_to].add(_value);
        emit Transfer(_from, _to, _value);
        //Delete any staking from contract address itself
        if (transferIns[_from].length > 0) delete transferIns[_from];
        //Store staking information for receivers
        uint64 _now = uint64(now);
        transferIns[_to].push(transferInStruct(uint128(_value), _now));
        //Payment Escrow Refunded
        payment.status = PaymentStatus.Refunded;
        //Emit Event
        emit PaymentCompletion(_orderId, payment.provider, payment.customer, payment.value, payment.status);

        return true;
    }

    /**
     * @dev approveRefund
     * @dev Allow a user to approve a refund
     * @param _orderId Ticket number of the escrow service
     */
    function approveRefund(uint _orderId) external returns(bool) {
        //Get Payment Handler
        Payment storage payment = payments[_orderId];
        //Only if pending
        require(payment.status == PaymentStatus.Pending);
        //Only owner or costumer
        require(msg.sender == owner || msg.sender == payment.customer);
        //Approve Refund
        payment.refundApproved = true;

        return true;
    }
    //ESCROW SECTION END
}