// File: OSnap.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "./SafeMath.sol";

contract OSnap {
    using SafeMath for uint256;

    // IPFS / Filecoin Reference
    struct Multihash {
        bytes32 digest;
        uint8 hashFunction;
        uint8 size;
    }

    // Emits when a new post is added
    event Post(
        uint256 indexed postID,
        address indexed postedBy,
        uint256 tipAPostID,
        uint256 tipAAmt,
        uint256 tipBPostID,
        uint256 tipBAmt,
        bytes32 digest,
        uint8 hashFunction,
        uint8 size
    );

    // Internal datastore.
    mapping(uint256 => Multihash) private posts;
    mapping(uint256 => address payable) private payableByPostID;
    mapping(address => uint256[]) private postsByAddress;

    // Global postID increments after each posting.
    uint256 private postID = 0;

    // Used to bootstap tangle.
    address payable private _bootstapAddr;

    constructor() public {
        _bootstapAddr = msg.sender;
    }

    /* Returns the current postID */
    function getPostID() public view returns (uint256 id) {
        return postID;
    }

    /* Returns a post multihash by postID reference. */
    function getPostById(uint256 _id)
        public
        view
        returns (
            bytes32 digest,
            uint8 hashFunction,
            uint8 size
        )
    {
        // Require valid post identifier.
        require(_id < postID, "Post not found.");

        // Return post multihash.
        Multihash storage post = posts[_id];
        return (post.digest, post.hashFunction, post.size);
    }

    /* Returns a post multihash by address:idx reference. */
    function getPostByAddressIdx(address _addr, uint256 _idx)
        public
        view
        returns (
            bytes32 digest,
            uint8 hashFunction,
            uint8 size
        )
    {
        // Require valid post address & index.
        require(postsByAddress[_addr].length > 0, "Address not found.");
        require(_idx < postsByAddress[_addr].length, "Post not found.");

        // Return post multihash.
        Multihash storage post = posts[postsByAddress[_addr][_idx]];
        return (post.digest, post.hashFunction, post.size);
    }

    /* Returns the number of multihashes posted by an address. */
    function getTotalPostsByAddress(address _addr)
        public
        view
        returns (uint256 length)
    {
        return postsByAddress[_addr].length;
    }

    /* Returns the original poster by postID */
    function getOPByID(uint256 _postID)
        public
        view
        returns (address payable poster)
    {
        return payableByPostID[_postID];
    }

    function _addTip(uint256 _postID, uint256 _tip) private {
        // Validate tip transaction.
        require(_tip > 0, "Missing tip value.");
        require(
            msg.sender == _bootstapAddr || (_postID < postID),
            "Post not found."
        );
        require(
            msg.sender == _bootstapAddr ||
                (payableByPostID[_postID] != msg.sender),
            "Self-tipping is discouraged."
        );

        // Process tip.
        payableByPostID[_postID].transfer(_tip);
    }

    /* Adds a tip. */
    function addTip(uint256 _postID) public payable {
        _addTip(_postID, msg.value);
    }

    /* Adds a new post to the tangle. */
    function addPost(
        uint256 _tipAPostId,
        uint256 _tipAAmt,
        uint256 _tipBPostId,
        uint256 _tipBAmt,
        bytes32 _digest,
        uint8 _hashFunction,
        uint8 _size
    ) public payable returns (uint256 id) {
        // Validate tip tangle amounts.
        require(
            _tipAAmt + _tipBAmt == msg.value,
            "Tips should equal message value."
        );

        // Add tips to tangle.
        _addTip(_tipAPostId, _tipAAmt);
        _addTip(_tipBPostId, _tipBAmt);

        // Construct the multihash object.
        Multihash memory post = Multihash(_digest, _hashFunction, _size);

        // Assign the postID and append it to the postsByAddress list.
        posts[postID] = post;
        postsByAddress[msg.sender].push(postID);
        payableByPostID[postID] = msg.sender;

        // Emit the Post event.
        emit Post(
            postID,
            msg.sender,
            _tipAPostId,
            _tipAAmt,
            _tipBPostId,
            _tipBAmt,
            _digest,
            _hashFunction,
            _size
        );

        // Increment the postID and return its value.
        return postID++;
    }
}

// File: SafeMath.sol



library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot overflow.
     */
    function sub(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;

        return c;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function div(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, "SafeMath: modulo by zero");
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts with custom message when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function mod(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }
}