package org.ethereum.vm;

import org.ethereum.crypto.ECKey;
import org.ethereum.crypto.HashUtil;
import org.ethereum.util.ByteUtil;

/**
 * @author Roman Mandeleil
 * @since 09.01.2015
 */
public class PrecompiledContracts {

    private static ECRecover ecRecover = new ECRecover();
    private static Sha256 sha256 = new Sha256();
    private static Ripempd160 ripempd160 = new Ripempd160();
    private static Identity identity = new Identity();


    public static PrecompiledContract getContractForAddress(DataWord address) {

        if (address == null) return identity;
        if (address.isHex("0000000000000000000000000000000000000000000000000000000000000001")) return ecRecover;
        if (address.isHex("0000000000000000000000000000000000000000000000000000000000000002")) return sha256;
        if (address.isHex("0000000000000000000000000000000000000000000000000000000000000003")) return ripempd160;
        if (address.isHex("0000000000000000000000000000000000000000000000000000000000000004")) return identity;

        return null;
    }


    public static abstract class PrecompiledContract {
        public abstract long getGasForData(byte[] data);

        public abstract byte[] execute(byte[] data);
    }

    public static class Identity extends PrecompiledContract {

        public Identity() {
        }

        @Override
        public long getGasForData(byte[] data) {

            // gas charge for the execution:
            // minimum 1 and additional 1 for each 32 bytes word (round  up)
            if (data == null) return 1;
            return 1 + (data.length + 31) / 32 * 1;
        }

        @Override
        public byte[] execute(byte[] data) {
            return data;
        }
    }

    public static class Sha256 extends PrecompiledContract {


        @Override
        public long getGasForData(byte[] data) {

            // gas charge for the execution:
            // minimum 50 and additional 50 for each 32 bytes word (round  up)
            if (data == null) return 50;
            return 50 + (data.length + 31) / 32 * 50;
        }

        @Override
        public byte[] execute(byte[] data) {

            if (data == null) return HashUtil.sha256(ByteUtil.EMPTY_BYTE_ARRAY);
            return HashUtil.sha256(data);
        }
    }


    public static class Ripempd160 extends PrecompiledContract {


        @Override
        public long getGasForData(byte[] data) {

            // gas charge for the execution:
            // minimum 50 and additional 50 for each 32 bytes word (round  up)
            if (data == null) return 50;
            return 50 + (data.length + 31) / 32 * 50;
        }

        @Override
        public byte[] execute(byte[] data) {

            byte[] result = null;
            if (data == null) result = HashUtil.ripemd160(ByteUtil.EMPTY_BYTE_ARRAY);
            else result = HashUtil.ripemd160(data);

            return new DataWord(result).getData();
        }
    }


    public static class ECRecover extends PrecompiledContract {

        @Override
        public long getGasForData(byte[] data) {
            return 500;
        }

        @Override
        public byte[] execute(byte[] data) {

            byte[] h = new byte[32];
            byte[] v = new byte[32];
            byte[] r = new byte[32];
            byte[] s = new byte[32];

            DataWord out = null;

            try {
                System.arraycopy(data, 0, h, 0, 32);
                System.arraycopy(data, 32, v, 0, 32);
                System.arraycopy(data, 64, r, 0, 32);
                System.arraycopy(data, 96, s, 0, 32);


                ECKey.ECDSASignature signature = ECKey.ECDSASignature.fromComponents(r, s, v[31]);

                ECKey key = ECKey.signatureToKey(h, signature.toBase64());
                out = new DataWord(key.getAddress());
            } catch (Throwable any) {
            }

            if (out == null) out = new DataWord(0);

            return out.getData();
        }
    }


}

@@ -1554,16 +1554,17 @@
 return 1
+5
 ;%0A      
@@ -1577,16 +1577,17 @@
 return 1
+5
  + (data
@@ -1607,17 +1607,17 @@
  / 32 * 
-1
+3
 ;%0A      
@@ -2010,33 +2010,33 @@
 == null) return 
-5
+6
 0;%0A            r
@@ -2033,33 +2033,33 @@
          return 
-5
+6
 0 + (data.length
@@ -2064,34 +2064,34 @@
 th + 31) / 32 * 
-50
+12
 ;%0A        %7D%0A%0A   
@@ -2588,17 +2588,18 @@
  return 
-5
+60
 0;%0A     
@@ -2612,17 +2612,18 @@
  return 
-5
+60
 0 + (dat
@@ -2644,17 +2644,18 @@
  / 32 * 
-5
+12
 0;%0A     
@@ -3117,17 +3117,18 @@
  return 
-5
+30
 00;%0A    

Support for new gas in sha/ripemd/ecrecover/identity