/*
 * gpj - Global Platform for Java SmartCardIO
 *
 * Copyright (C) 2009 Wojciech Mostowski, [email protected]
 * Copyright (C) 2009 Francois Kooman, [email protected]
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3.0 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 */

package pro.javacard.gp;

import apdu4j.HexUtils;
import com.payneteasy.tlv.BerTlvLogger;
import com.payneteasy.tlv.BerTlvParser;
import com.payneteasy.tlv.BerTlvs;
import com.payneteasy.tlv.IBerTlvLogger;
import org.slf4j.Logger;

import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class GPUtils {

    public static int intValue(String s) {
        if (s.trim().toLowerCase().startsWith("0x")) {
            return Integer.parseInt(s.substring(2), 16);
        }
        return Integer.parseInt(s, 10);
    }

    public static String byteArrayToReadableString(byte[] array) {
        if (array == null) {
            return "(null)";
        }
        StringBuffer s = new StringBuffer();
        for (int i = 0; i < array.length; i++) {
            char c = (char) array[i];
            s.append(((c >= 0x20) && (c < 0x7f)) ? (c) : ("."));
        }
        return "|" + s.toString() + "|";
    }

    public static byte[] concatenate(byte[]... args) {
        int length = 0, pos = 0;
        for (byte[] arg : args) {
            length += arg.length;
        }
        byte[] result = new byte[length];
        for (byte[] arg : args) {
            System.arraycopy(arg, 0, result, pos, arg.length);
            pos += arg.length;
        }
        return result;
    }

    public static List<byte[]> splitArray(byte[] array, int blockSize) {
        List<byte[]> result = new ArrayList<byte[]>();

        int len = array.length;
        int offset = 0;
        int left = len - offset;
        while (left > 0) {
            int currentLen = 0;
            if (left >= blockSize) {
                currentLen = blockSize;
            } else {
                currentLen = left;
            }
            byte[] block = new byte[currentLen];
            System.arraycopy(array, offset, block, 0, currentLen);
            result.add(block);
            left -= currentLen;
            offset += currentLen;
        }
        return result;
    }

    public static byte[] encodeLength(int len) {
        ByteArrayOutputStream bo = new ByteArrayOutputStream();
        // XXX: can probably re-use some existing method somewhere
        if (len < 0x80) {
            bo.write((byte) len);
        } else if (len <= 0xFF) {
            bo.write((byte) 0x81);
            bo.write((byte) len);
        } else if (len <= 0xFFFF) {
            bo.write((byte) 0x82);
            bo.write((byte) ((len & 0xFF00) >> 8));
            bo.write((byte) (len & 0xFF));
        } else {
            bo.write((byte) 0x83);
            bo.write((byte) ((len & 0xFF0000) >> 16));
            bo.write((byte) ((len & 0xFF00) >> 8));
            bo.write((byte) (len & 0xFF));
        }
        return bo.toByteArray();
    }

    // Encodes APDU LC value, which has either length of 1 byte or 3 bytes (for extended length APDUs)
    // If LC or LE is bigger than fits in one byte (255), LC must be encoded in three bytes
    public static byte[] encodeLcLength(int lc, int le) {
        if (lc > 255 || le > 256) {
            byte[] lc_ba = ByteBuffer.allocate(4).putInt(lc).array();
            return Arrays.copyOfRange(lc_ba, 1, 4);
        } else
            return new byte[]{(byte) lc};
    }

    // Assumes the bignum length must be even
    static byte[] positive(byte[] bytes) {
        if (bytes[0] == 0 && bytes.length % 2 == 1) {
            return Arrays.copyOfRange(bytes, 1, bytes.length);
        }
        return bytes;
    }

    // JavaCard requires values without sign byte (assumed positive)
    static byte[] positive(BigInteger i) {
        byte[] bytes = i.toByteArray();
        return positive(bytes);
    }

    static void trace_lv(byte[] data, Logger logger) {
        for (int i = 0; i < data.length; ) {
            int l= data[i] & 0xFF;
            logger.trace(String.format("[%02X] %s", l, HexUtils.bin2hex(Arrays.copyOfRange(data, i + 1, i + 1 + l))));
            i += 1 + l;
        }
    }

    static void trace_tlv(byte[] data, Logger l) {
        BerTlvParser parser = new BerTlvParser();
        BerTlvs tlvs = parser.parse(data);
        BerTlvLogger.log("", tlvs,
                new IBerTlvLogger() {
                    @Override
                    public boolean isDebugEnabled() {
                        return true;
                    }

                    @Override
                    public void debug(String s, Object... objects) {
                        l.trace(s, objects);
                    }
                }
        );
    }
}