<commit_before>package foundation.omni.rpc;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.msgilligan.bitcoinj.rpc.BitcoinExtendedClient;
import foundation.omni.json.pojo.OmniPropertyInfo;
import org.consensusj.jsonrpc.JsonRpcException;
import com.msgilligan.bitcoinj.rpc.RpcConfig;
import foundation.omni.CurrencyID;
import foundation.omni.Ecosystem;
import foundation.omni.OmniValue;
import foundation.omni.PropertyType;
import foundation.omni.json.conversion.OmniClientModule;
import foundation.omni.net.OmniNetworkParameters;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Sha256Hash;

import java.io.IOException;
import java.math.BigDecimal;
import java.net.URI;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;

// TODO: add missing RPCs:
// - omni_listtransactions
// - omni_gettradehistoryforpair
// - omni_gettradehistoryforaddress

/**
 * Pure Java Bitcoin and Omni Core JSON-RPC client with camelCase method names.
 * <p>
 * For example, if the RPC is {@code "omni_getbalance"}, then the corresponding method name is {@link #omniGetBalance(Address, CurrencyID)}.
 *
 * @see <a href="https://github.com/OmniLayer/omnicore/blob/master/src/omnicore/doc/rpc-api.md#json-rpc-api">Omni Core JSON RPC API documentation on GitHub</a>
 */
public class OmniClient extends BitcoinExtendedClient {

    public static Sha256Hash zeroHash = Sha256Hash.wrap("0000000000000000000000000000000000000000000000000000000000000000");
    private DecimalFormat jsonDecimalFormat;

    public OmniClient(RpcConfig config) throws IOException {
        this(config.getNetParams(), config.getURI(), config.getUsername(), config.getPassword());
    }

    public OmniClient(NetworkParameters netParams, URI server, String rpcuser, String rpcpassword) {
        super(netParams, server, rpcuser, rpcpassword);
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.registerModule(new OmniClientModule(getNetParams()));
        // Create a DecimalFormat that fits our requirements
        DecimalFormatSymbols symbols = new DecimalFormatSymbols();
        symbols.setGroupingSeparator(',');
        symbols.setDecimalSeparator('.');
        String pattern = "#,##0.0#";
        jsonDecimalFormat = new DecimalFormat(pattern, symbols);
        jsonDecimalFormat.setParseBigDecimal(true);
    }

    public OmniNetworkParameters getOmniNetParams() {
        return OmniNetworkParameters.fromBitcoinParms(getNetParams());
    }

    /**
     * Returns various state information of Omni Core and the Omni Layer protocol.
     *
     * @return An object with state information
     * @throws JsonRpcException JSON RPC error
     * @throws IOException network error
     */
    public Map<String, Object> omniGetInfo() throws JsonRpcException, IOException {
        Map<String, Object> result = send("omni_getinfo");
        return result;
    }

    /**
     * Lists all currencies, smart properties and tokens.
     *
     * @return A list with short information
     * @throws JsonRpcException JSON RPC error
     * @throws IOException network error
     */
    public List<SmartPropertyListInfo> omniListProperties() throws JsonRpcException, IOException {
        JavaType resultType = mapper.getTypeFactory().constructCollectionType(List.class, SmartPropertyListInfo.class);
        return send("omni_listproperties", resultType);
    }

    /**
     * Returns information about the specified currency, property, or token.
     *
     * @param currency The identifier to look up
     * @return Omni Smart Property Info
     * @throws JsonRpcException JSON RPC error
     * @throws IOException network error
     */
    public OmniPropertyInfo omniGetProperty(CurrencyID currency) throws JsonRpcException, IOException {
        OmniPropertyInfo result = send("omni_getproperty", OmniPropertyInfo.class, currency);
        return result;
    }

    /**
     * Returns information about a crowdsale.
     *
     * @param currency The identifier of the crowdsale
     * @return An object with detailed information
     * @throws JsonRpcException JSON RPC error
     * @throws IOException network error
     */
    public Map<String, Object> omniGetCrowdsale(CurrencyID currency) throws JsonRpcException, IOException {
        Map<String, Object> result = send("omni_getcrowdsale", currency);
        return result;
    }

    /**
     * Lists currently active crowdsales.
     *
     * @return A list with information about active crowdsales
     * @throws JsonRpcException JSON RPC error
     * @throws IOException network error
     */
    public List<Map<String, Object>> omniGetActiveCrowdsales() throws JsonRpcException, IOException {
        List<Map<String, Object>> result = send("omni_getactivecrowdsales");
        return result;
    }

    /**
     * Lists currently active offers on the distributed BTC/OMNI exchange.
     *
     * @return A list with information about the active offers
     * @throws JsonRpcException JSON RPC error
     * @throws IOException network error
     */
    public List<Map<String, Object>> omniGetActiveDExSells() throws JsonRpcException, IOException {
        List<Map<String, Object>> result = send("omni_getactivedexsells");
        return result;
    }

    /**
     * Returns the balance for a given address and property.
     *
     * @param address  The address to look up
     * @param currency The identifier of the token to look up
     * @return The available and reserved balance
     * @throws JsonRpcException JSON RPC error
     * @throws IOException network error
     */
    public BalanceEntry omniGetBalance(Address address, CurrencyID currency)
            throws JsonRpcException, IOException {
        return send("omni_getbalance", BalanceEntry.class, address, currency.getValue());
    }

    /**
     * Returns a list of balances for a given identifier.
     *
     * @param currency The identifier of the token to look up
     * @return A Sorted Map indexed by addresses to available and reserved balances
     * @throws JsonRpcException JSON RPC error
     * @throws IOException network error
     */
    public SortedMap<Address, BalanceEntry> omniGetAllBalancesForId(CurrencyID currency)
            throws JsonRpcException, IOException {
        return send("omni_getallbalancesforid", AddressBalanceEntries.class, currency);
    }

    /**
     * Returns a list of all token balances for a given address.
     *
     * @param address The address to look up
     * @return A Sorted Map indexed by currency/propertyid to available and reserved balances
     * @throws JsonRpcException JSON RPC error
     * @throws IOException network error
     */
    public SortedMap<CurrencyID, BalanceEntry> omniGetAllBalancesForAddress(Address address)
            throws JsonRpcException, IOException {
        return send("omni_getallbalancesforaddress", PropertyBalanceEntries.class, address);
    }

    /**
     * Returns information about an Omni Layer transaction.<commit_msg>workaround for bitcoinj compareTo issue<commit_after>package foundation.omni.rpc;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.google.common.primitives.UnsignedBytes;
import com.msgilligan.bitcoinj.rpc.BitcoinExtendedClient;
import foundation.omni.json.pojo.OmniPropertyInfo;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.SegwitAddress;
import org.consensusj.jsonrpc.JsonRpcException;
import com.msgilligan.bitcoinj.rpc.RpcConfig;
import foundation.omni.CurrencyID;
import foundation.omni.Ecosystem;
import foundation.omni.OmniValue;
import foundation.omni.PropertyType;
import foundation.omni.json.conversion.OmniClientModule;
import foundation.omni.net.OmniNetworkParameters;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Sha256Hash;

import java.io.IOException;
import java.math.BigDecimal;
import java.net.URI;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

// TODO: add missing RPCs:
// - omni_listtransactions
// - omni_gettradehistoryforpair
// - omni_gettradehistoryforaddress

/**
 * Pure Java Bitcoin and Omni Core JSON-RPC client with camelCase method names.
 * <p>
 * For example, if the RPC is {@code "omni_getbalance"}, then the corresponding method name is {@link #omniGetBalance(Address, CurrencyID)}.
 *
 * @see <a href="https://github.com/OmniLayer/omnicore/blob/master/src/omnicore/doc/rpc-api.md#json-rpc-api">Omni Core JSON RPC API documentation on GitHub</a>
 */
public class OmniClient extends BitcoinExtendedClient {

    public static Sha256Hash zeroHash = Sha256Hash.wrap("0000000000000000000000000000000000000000000000000000000000000000");
    private DecimalFormat jsonDecimalFormat;

    public OmniClient(RpcConfig config) throws IOException {
        this(config.getNetParams(), config.getURI(), config.getUsername(), config.getPassword());
    }

    public OmniClient(NetworkParameters netParams, URI server, String rpcuser, String rpcpassword) {
        super(netParams, server, rpcuser, rpcpassword);
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.registerModule(new OmniClientModule(getNetParams()));
        // Create a DecimalFormat that fits our requirements
        DecimalFormatSymbols symbols = new DecimalFormatSymbols();
        symbols.setGroupingSeparator(',');
        symbols.setDecimalSeparator('.');
        String pattern = "#,##0.0#";
        jsonDecimalFormat = new DecimalFormat(pattern, symbols);
        jsonDecimalFormat.setParseBigDecimal(true);
    }

    public OmniNetworkParameters getOmniNetParams() {
        return OmniNetworkParameters.fromBitcoinParms(getNetParams());
    }

    /**
     * Returns various state information of Omni Core and the Omni Layer protocol.
     *
     * @return An object with state information
     * @throws JsonRpcException JSON RPC error
     * @throws IOException network error
     */
    public Map<String, Object> omniGetInfo() throws JsonRpcException, IOException {
        Map<String, Object> result = send("omni_getinfo");
        return result;
    }

    /**
     * Lists all currencies, smart properties and tokens.
     *
     * @return A list with short information
     * @throws JsonRpcException JSON RPC error
     * @throws IOException network error
     */
    public List<SmartPropertyListInfo> omniListProperties() throws JsonRpcException, IOException {
        JavaType resultType = mapper.getTypeFactory().constructCollectionType(List.class, SmartPropertyListInfo.class);
        return send("omni_listproperties", resultType);
    }

    /**
     * Returns information about the specified currency, property, or token.
     *
     * @param currency The identifier to look up
     * @return Omni Smart Property Info
     * @throws JsonRpcException JSON RPC error
     * @throws IOException network error
     */
    public OmniPropertyInfo omniGetProperty(CurrencyID currency) throws JsonRpcException, IOException {
        OmniPropertyInfo result = send("omni_getproperty", OmniPropertyInfo.class, currency);
        return result;
    }

    /**
     * Returns information about a crowdsale.
     *
     * @param currency The identifier of the crowdsale
     * @return An object with detailed information
     * @throws JsonRpcException JSON RPC error
     * @throws IOException network error
     */
    public Map<String, Object> omniGetCrowdsale(CurrencyID currency) throws JsonRpcException, IOException {
        Map<String, Object> result = send("omni_getcrowdsale", currency);
        return result;
    }

    /**
     * Lists currently active crowdsales.
     *
     * @return A list with information about active crowdsales
     * @throws JsonRpcException JSON RPC error
     * @throws IOException network error
     */
    public List<Map<String, Object>> omniGetActiveCrowdsales() throws JsonRpcException, IOException {
        List<Map<String, Object>> result = send("omni_getactivecrowdsales");
        return result;
    }

    /**
     * Lists currently active offers on the distributed BTC/OMNI exchange.
     *
     * @return A list with information about the active offers
     * @throws JsonRpcException JSON RPC error
     * @throws IOException network error
     */
    public List<Map<String, Object>> omniGetActiveDExSells() throws JsonRpcException, IOException {
        List<Map<String, Object>> result = send("omni_getactivedexsells");
        return result;
    }

    /**
     * Returns the balance for a given address and property.
     *
     * @param address  The address to look up
     * @param currency The identifier of the token to look up
     * @return The available and reserved balance
     * @throws JsonRpcException JSON RPC error
     * @throws IOException network error
     */
    public BalanceEntry omniGetBalance(Address address, CurrencyID currency)
            throws JsonRpcException, IOException {
        return send("omni_getbalance", BalanceEntry.class, address, currency.getValue());
    }

    /**
     * Returns a sorted map of address-balances for a given identifier.
     *
     * @param currency The identifier of the token to look up
     * @return A Sorted Map indexed by addresses to available and reserved balances
     * @throws JsonRpcException JSON RPC error
     * @throws IOException network error
     */
    public SortedMap<Address, BalanceEntry> omniGetAllBalancesForId(CurrencyID currency)
            throws JsonRpcException, IOException {
        //return send("omni_getallbalancesforid", AddressBalanceEntries.class, currency);
        return omniGetAllBalancesForIdAsList(currency).stream()
                .collect(() -> new TreeMap<>(OmniClient::addressComparatorWorkaround),
                        (map, abe) -> map.put(abe.getAddress(), new BalanceEntry(abe.balance, abe.reserved, abe.frozen)),
                        TreeMap::putAll);
    }

    /**
     * Workaround comparator for problem with {@code compareTo} in bitcoinj {@code LegacyAddress}
     * @param a First address to compare
     * @param b Second address to compare
     * @return A comparision for sorting
     */
    private static int addressComparatorWorkaround(Address a, Address b) {
        // First compare netParams
        int result = a.getParameters().getId().compareTo(b.getParameters().getId());
        if (result != 0) return result;

        // Then compare Legacy vs Segwit
        if (b instanceof SegwitAddress) {
            return -1;  // Legacy addresses (starting with 1 or 3) come before Segwit addresses.
        }

        // Next compare version byte and finally the {@code bytes} field itself
        result = Integer.compare(((LegacyAddress)a).getVersion(), ((LegacyAddress) b).getVersion());
        return result != 0 ? result : UnsignedBytes.lexicographicalComparator().compare(((LegacyAddress)a).getHash(), b.getHash());
    }

    /**
     * Returns a list of address-balances for a given identifier.
     *
     * @param currency The identifier of the token to look up
     * @return A list of address-balances
     * @throws JsonRpcException JSON RPC error
     * @throws IOException network error
     */
    public List<AddressBalanceEntry> omniGetAllBalancesForIdAsList(CurrencyID currency)
            throws JsonRpcException, IOException {
        JavaType resultType = mapper.getTypeFactory().constructCollectionType(List.class, AddressBalanceEntry.class);
        return send("omni_getallbalancesforid", resultType, currency);
    }

    /**
     * Returns a list of all token balances for a given address.
     *
     * @param address The address to look up
     * @return A Sorted Map indexed by currency/propertyid to available and reserved balances
     * @throws JsonRpcException JSON RPC error
     * @throws IOException network error
     */
    public SortedMap<CurrencyID, BalanceEntry> omniGetAllBalancesForAddress(Address address)
            throws JsonRpcException, IOException {
        return send("omni_getallbalancesforaddress", PropertyBalanceEntries.class, address);
    }

    /**
     * Returns information about an Omni Layer transaction.
{'fraction_non_alphanumeric': 0.08389200942199673, 'fraction_numerical': 0.009361599323548951, 'mean_word_length': 3.530232558139535, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 43, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 4}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2768297', 'n_tokens_mistral': 4605, 'n_tokens_neox': 4382, 'n_words': 1286}