contract RedEnvelopes is usingOraclize {
   struct RedEnvelope {
       uint value;
       uint maxValuePerOpen;
       string giver;

       uint balance;
       mapping (address=>uint) takers;
   }

   mapping (string=>RedEnvelope) envelopes;

   mapping (bytes32=>string) pendingQueries;
   mapping (string=>address) /* public */ forumIdToAddr;
   mapping (address=>string) public addrToForumId;
   uint constant maxAllowedId = 253950;

   // gas estimation: 105340
   function give (string name, uint maxValuePerOpen, string giver) payable {
       RedEnvelope storage e = envelopes[name];
       require(e.value == 0);
       require(msg.value > 0);
       e.balance = e.value = msg.value;
       e.maxValuePerOpen = maxValuePerOpen;
       e.giver = giver;
   }

   function stringToUint(string s) constant internal returns (uint result) {
       bytes memory b = bytes(s);
       uint i;
       result = 0;
       for (i = 0; i < b.length; i++) {
           uint c = uint(b[i]);
           if (c >= 48 && c <= 57) {
               result = result * 10 + (c - 48);
           } else {
               revert();
           }
       }
   }

   function concat (string a, string b, string c) internal constant  returns (string) {
       bytes memory ba = bytes(a);
       bytes memory bb = bytes(b);
       bytes memory bc = bytes(c);
       string memory ab = new string(ba.length+bb.length+bc.length);
       bytes memory buf = bytes(ab);
       uint p = 0;
       for (uint i=0; i<ba.length; i++) {
           buf[p] = ba[i];
           p++;
       }
       for (i=0; i<bb.length; i++) {
           buf[p] = bb[i];
           p++;
       }
       for (i=0; i<bc.length; i++) {
           buf[p] = bc[i];
           p++;
       }
       return ab;
   }
  
   function authenticate (string forumId) payable {
       _authenticate(forumId, 150000); // by experiment; see comment on __callback
   }
      
   // verify that the sender address does belong to a forum user
   function _authenticate (string forumId, uint cbGasLimit) payable {
       require(stringToUint(forumId) < maxAllowedId);
       require(forumIdToAddr[forumId] == 0);
       require(msg.value >= oraclize_getPrice("URL", cbGasLimit));

       // looks like the page is too broken for oralize's xpath handler
       // bytes32 queryId = oraclize_query("URL", 'html(http://8btc.com/home.php?mod=space&do=doing&uid=250950).xpath(//*[@id="ct"]//div[@class="xld xlda"]/dl[1]/dd[2]/span/text())');

       bytes32 queryId = oraclize_query("URL", 
            /*
            "json(http://redproxy-1.appspot.com/getAddress.php).addr",
            concat("x", forumId) // buggy oraclize
            */
            concat("json(http://redproxy-1.appspot.com/getAddress.php?uid=", forumId, ").addr"),
            cbGasLimit            
       );
       pendingQueries[queryId] = forumId;
   }

   // gas used by ganache simulation run: 96451
   function __callback (bytes32 queryId, string result) {
       require(msg.sender == oraclize_cbAddress());

       string memory forumId = pendingQueries[queryId];
       address addr = parseAddr(result);
       delete pendingQueries[queryId];
       
       forumIdToAddr[forumId] = addr;
       addrToForumId[addr] = forumId;
   }

   // for now we don't use real randomness
   // gas estimation: 57813 x 4 gwei = 0.0002356 eth * 1132 usd/eth = 0.27 usd
   function open (string envelopeName) returns (uint value) {
        string forumId = addrToForumId[msg.sender];
        require(bytes(forumId).length > 0);

        RedEnvelope e = envelopes[envelopeName];
        require(e.balance > 0);
        require(e.takers[msg.sender] == 0);

        value = uint(keccak256(envelopeName, msg.sender)) % e.maxValuePerOpen;

        if (value > e.balance) {
            value = e.balance;
        }
        e.balance -= value;
        e.takers[msg.sender] = value;
        msg.sender.transfer(value);
   }

   function _forumIdToAddr (string forumId) constant returns (address) {
       return forumIdToAddr[forumId];
   }

   function _envelopes (string name, address opener) constant returns (uint value, uint maxValuePerOpen, string giver, uint balance, uint valuePerOpen) {
       return (envelopes[name].value,
               envelopes[name].maxValuePerOpen,
               envelopes[name].giver,
               envelopes[name].balance,
               envelopes[name].takers[opener]);
   }

}
function open (string envelopeName) returns (uint value) {
     string forumId = addrToForumId[msg.sender];
     require(bytes(forumId).length > 0);

     RedEnvelope e = envelopes[envelopeName];
     require(e.balance > 0);
     require(e.takers[msg.sender] == 0);

     value = uint(keccak256(envelopeName, msg.sender)) % e.maxValuePerOpen;

     if (value > e.balance) {
         value = e.balance;
     }
     e.balance -= value;
     e.takers[msg.sender] = value;
     msg.sender.transfer(value);
}

// for now we don't use real randomness
// gas estimation: 57813 x 4 gwei = 0.0002356 eth * 1132 usd/eth = 0.27 usd