contract InterCrypto is Ownable, myUsingOraclize {
    // _______________VARIABLES_______________
    struct Conversion {
        address returnAddress;
        uint amount;
    }

    mapping (uint => Conversion) public conversions;
    uint conversionCount = 0;
    mapping (bytes32 => uint) oraclizeMyId2conversionID;
    mapping (address => uint) public recoverable;

    // _______________EVENTS_______________
    event ConversionStarted(uint indexed conversionID);
    event ConversionSentToShapeShift(uint indexed conversionID, address indexed returnAddress, address indexed depositAddress, uint amount);
    event ConversionAborted(uint indexed conversionID, string reason);
    event Recovered(address indexed recoveredTo, uint amount);

    // _______________EXTERNAL FUNCTIONS_______________
    /**
     * Constructor.
     */
    function InterCrypto() {}

    /**
     * Destroys the contract and returns and Ether to the owner.
     */
    function kill() onlyOwner external {
        selfdestruct(owner);
    }

    /**
     * Fallback function to allow contract to accept Ether.
     */
    function () payable {}

    /**
     * Sets up a ShapeShift cryptocurrency conversion using Oraclize and the ShapeShift API. Must be sent more Ether than the Oraclize price.
     * Returns a conversionID which can be used for tracking of the conversion.
     * @param _coinSymbol The coinsymbol of the other blockchain to be used by ShapeShift. See engine() function for more details.
     * @param _toAddress The address on the other blockchain that the converted cryptocurrency will be sent to.
     */
    function convert1(string _coinSymbol, string _toAddress) external payable returns(uint) {
        return engine(_coinSymbol, _toAddress, msg.sender);
    }

    /**
     * Sets up a ShapeShift cryptocurrency conversion using Oraclize and the ShapeShift API. Must be sent more Ether than the Oraclize price.
     * Returns a conversionID which can be used for tracking of the conversion.
     * @param _coinSymbol The coinsymbol of the other blockchain to be used by ShapeShift. See engine() function for more details.
     * @param _toAddress The address on the other blockchain that the converted cryptocurrency will be sent to.
     * @param _returnAddress The Ethereum address that any Ether should be sent back to in the event that the ShapeShift conversion is invalid or fails
     */
    function convert2(string _coinSymbol, string _toAddress, address _returnAddress) external payable returns(uint) {
        return engine(_coinSymbol, _toAddress, _returnAddress);
    }

    /**
     * Callback function for use exclusively by Oraclize.
     * @param myid The Oraclize id of the query.
     * @param result The result of the query.
     */
    function __callback(bytes32 myid, string result) {
        if (msg.sender != oraclize.cbAddress()) revert();

        uint conversionID = oraclizeMyId2conversionID[myid];

        if( bytes(result).length == 0 ) {
            ConversionAborted(conversionID, "Oraclize return value was invalid, this is probably due to incorrect convert() argments");
            recoverable[conversions[conversionID].returnAddress] += conversions[conversionID].amount;
            conversions[conversionID].amount = 0;
        }
        else {
            address depositAddress = parseAddr(result);
            require(depositAddress != msg.sender); // prevent DAO tpe re-entracy vulnerability that can potentially be done by Oraclize
            uint sendAmount = conversions[conversionID].amount;
            conversions[conversionID].amount = 0;
            if (depositAddress.send(sendAmount)) {
                ConversionSentToShapeShift(conversionID, conversions[conversionID].returnAddress, depositAddress, sendAmount);
            }
            else {
                ConversionAborted(conversionID, "deposit to address returned by Oraclize failed");
                recoverable[conversions[conversionID].returnAddress] += sendAmount;
            }
        }
    }

    /**
     * Cancel a cryptocurrency conversion.
     * This should only be required to be called if Oraclize fails make a return call to __callback().
     * @param conversionID The conversion ID of the cryptocurrency conversion, generated during engine().
     */
     function cancelConversion(uint conversionID) external {
        Conversion memory conversion = conversions[conversionID];

        if (conversion.amount > 0) {
            require(msg.sender == conversion.returnAddress);
            recoverable[msg.sender] += conversion.amount;
            conversions[conversionID].amount = 0;
            ConversionAborted(conversionID, "conversion cancelled by creator");
        }
    }

    /**
     * Recover any recoverable funds due to the failure of InterCrypto. Failure can occure due to:
     * 1. Bad user inputs to convert().
     * 2. ShapeShift temporarily or permanently discontinues support of other blockchain.
     * 3. ShapeShift service becomes unavailable.
     * 4. Oraclize service become unavailable.
     */
     function recover() external {
        uint amount = recoverable[msg.sender];
        recoverable[msg.sender] = 0;
        if (msg.sender.send(amount)) {
            Recovered(msg.sender, amount);
        }
        else {
            recoverable[msg.sender] = amount;
        }
    }
    // _______________PUBLIC FUNCTIONS_______________
    /**
     * Returns the price in Wei paid to Oraclize.
     */
    function getInterCryptoPrice() constant public returns (uint) {
        return oraclize_getPrice('URL');
    }

    // _______________INTERNAL FUNCTIONS_______________
    /**
     * Sets up a ShapeShift cryptocurrency conversion using Oraclize and the ShapeShift API. Must be sent more Ether than the Oraclize price.
     * Returns a conversionID which can be used for tracking of the conversion.
     * @param _coinSymbol The coinsymbol of the other blockchain to be used by ShapeShift. See engine() function for more details.
     * @param _toAddress The address on the other blockchain that the converted cryptocurrency will be sent to.
     * Example first two arguments:
     * "ltc", "LbZcDdMeP96ko85H21TQii98YFF9RgZg3D"    Litecoin
     * "btc", "1L8oRijgmkfcZDYA21b73b6DewLtyYs87s"    Bitcoin
     * "dash", "Xoopows17idkTwNrMZuySXBwQDorsezQAx"   Dash
     * "zec", "t1N7tf1xRxz5cBK51JADijLDWS592FPJtya"   ZCash
     * "doge", "DMAFvwTH2upni7eTau8au6Rktgm2bUkMei"   Dogecoin
     * Test symbol pairs using ShapeShift API (shapeshift.io/validateAddress/[address]/[coinSymbol]) or by creating a test
     * conversion on https://shapeshift.io first whenever possible before using it with InterCrypto.
     * @param _returnAddress The Ethereum address that any Ether should be sent back to in the event that the ShapeShift conversion is invalid or fails.
     */
    function engine(string _coinSymbol, string _toAddress, address _returnAddress) internal returns(uint conversionID) {
        conversionID = conversionCount++;

        if (
            !isValidString(_coinSymbol, 6) || // Waves smbol is "waves"
            !isValidString(_toAddress, 120)   // Monero integrated addresses are 106 characters
            ) {
            ConversionAborted(conversionID, "input parameters are too long or contain invalid symbols");
            recoverable[msg.sender] += msg.value;
            return;
        }

        uint oraclizePrice = getInterCryptoPrice();

        if (msg.value > oraclizePrice) {
            Conversion memory conversion = Conversion(_returnAddress, msg.value-oraclizePrice);
            conversions[conversionID] = Conversion(_returnAddress, msg.value-oraclizePrice);

            string memory postData = createShapeShiftConversionPost(_coinSymbol, _toAddress);
            bytes32 myQueryId = oraclize_query("URL", "json(https://shapeshift.io/shift).deposit", postData);

            if (myQueryId == 0) {
                ConversionAborted(conversionID, "unexpectedly high Oraclize price when calling oraclize_query");
                recoverable[msg.sender] += msg.value-oraclizePrice;
                conversions[conversionID].amount = 0;
                return;
            }
            oraclizeMyId2conversionID[myQueryId] = conversionID;
            ConversionStarted(conversionID);
        }
        else {
            ConversionAborted(conversionID, "Not enough Ether sent to cover Oraclize fee");
            conversions[conversionID].amount = 0;
            recoverable[msg.sender] += msg.value;
        }
    }

    /**
     * Returns true if a given string contains only numbers and letters, and is below a maximum length.
     * @param _string String to be checked.
     * @param maxSize The maximum allowable sting character length. The address on the other blockchain that the converted cryptocurrency will be sent to.
     */
    function isValidString(string _string, uint maxSize) constant internal returns (bool allowed) {
        bytes memory stringBytes = bytes(_string);
        uint lengthBytes = stringBytes.length;
        if (lengthBytes < 1 ||
            lengthBytes > maxSize) {
            return false;
        }

        for (uint i = 0; i < lengthBytes; i++) {
            byte b = stringBytes[i];
            if ( !(
                (b >= 48 && b <= 57) || // 0 - 9
                (b >= 65 && b <= 90) || // A - Z
                (b >= 97 && b <= 122)   // a - z
            )) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns a concatenation of seven bytes.
     * @param b1 The first bytes to be concatenated.
     * ...
     * @param b7 The last bytes to be concatenated.
     */
    function concatBytes(bytes b1, bytes b2, bytes b3, bytes b4, bytes b5, bytes b6, bytes b7) internal returns (bytes bFinal) {
        bFinal = new bytes(b1.length + b2.length + b3.length + b4.length + b5.length + b6.length + b7.length);

        uint i = 0;
        uint j;
        for (j = 0; j < b1.length; j++) bFinal[i++] = b1[j];
        for (j = 0; j < b2.length; j++) bFinal[i++] = b2[j];
        for (j = 0; j < b3.length; j++) bFinal[i++] = b3[j];
        for (j = 0; j < b4.length; j++) bFinal[i++] = b4[j];
        for (j = 0; j < b5.length; j++) bFinal[i++] = b5[j];
        for (j = 0; j < b6.length; j++) bFinal[i++] = b6[j];
        for (j = 0; j < b7.length; j++) bFinal[i++] = b7[j];
    }

    /**
     * Returns the ShapeShift shift API string that is needed to be sent to Oraclize.
     * @param _coinSymbol The coinsymbol of the other blockchain to be used by ShapeShift. See engine() function for more details.
     * @param _toAddress The address on the other blockchain that the converted cryptocurrency will be sent to.
     * Example output:
     * ' {"withdrawal":"LbZcDdMeP96ko85H21TQii98YFF9RgZg3D","pair":"eth_ltc","returnAddress":"558999ff2e0daefcb4fcded4c89e07fdf9ccb56c"}'
     * Note that an extra space ' ' is needed at the start to tell Oraclize to make a POST query
     */
    function createShapeShiftConversionPost(string _coinSymbol, string _toAddress) internal returns (string sFinal) {
        string memory s1 = ' {"withdrawal":"';
        string memory s3 = '","pair":"eth_';
        string memory s5 = '","returnAddress":"';
        string memory s7 = '"}';

        bytes memory bFinal = concatBytes(bytes(s1), bytes(_toAddress), bytes(s3), bytes(_coinSymbol), bytes(s5), bytes(addressToBytes(msg.sender)), bytes(s7));

        sFinal = string(bFinal);
    }

    /**
     * Returns the ASCII numeric or lower case character representation of a number.
     * Authored by from https://github.com/axic
     * @param nibble Nuber to be converted
     */
    function nibbleToChar(uint nibble) internal returns (uint ret) {
        if (nibble > 9)
        return nibble + 87; // nibble + 'a'- 10
        else
        return nibble + 48; // '0'
    }

    /**
     * Returns the bytes representation of a provided Ethereum address
     * Authored by from https://github.com/axic
     * @param _address Ethereum address to be cast to bytes
     */
    function addressToBytes(address _address) internal returns (bytes) {
        uint160 tmp = uint160(_address);

        // 40 bytes of space, but actually uses 64 bytes
        string memory holder = "                                        ";
        bytes memory ret = bytes(holder);

        // NOTE: this is written in an expensive way, as out-of-order array access
        //       is not supported yet, e.g. we cannot go in reverse easily
        //       (or maybe it is a bug: https://github.com/ethereum/solidity/issues/212)
        uint j = 0;
        for (uint i = 0; i < 20; i++) {
            uint _tmp = tmp / (2 ** (8*(19-i))); // shr(tmp, 8*(19-i))
            uint nb1 = (_tmp / 0x10) & 0x0f;     // shr(tmp, 8) & 0x0f
            uint nb2 = _tmp & 0x0f;
            ret[j++] = byte(nibbleToChar(nb1));
            ret[j++] = byte(nibbleToChar(nb2));
        }

        return ret;
    }

    // _______________PRIVATE FUNCTIONS_______________

}
/**
 * @title InterCrypto
 * @dev The InterCrypto offers a no-commission service using Oraclize and ShapeShift
 * that allows for on-blockchain conversion from Ether to any other blockchain that ShapeShift supports.
 * @author Jack Tanner - <jnt16@ic.ac.uk>
 */
function isValidString(string _string, uint maxSize) constant internal returns (bool allowed) {
    bytes memory stringBytes = bytes(_string);
    uint lengthBytes = stringBytes.length;
    if (lengthBytes < 1 ||
        lengthBytes > maxSize) {
        return false;
    }

    for (uint i = 0; i < lengthBytes; i++) {
        byte b = stringBytes[i];
        if ( !(
            (b >= 48 && b <= 57) || // 0 - 9
            (b >= 65 && b <= 90) || // A - Z
            (b >= 97 && b <= 122)   // a - z
        )) {
            return false;
        }
    }
    return true;
}

/**
 * Returns true if a given string contains only numbers and letters, and is below a maximum length.
 * @param _string String to be checked.
 * @param maxSize The maximum allowable sting character length. The address on the other blockchain that the converted cryptocurrency will be sent to.
 */