/*
 * The MIT License
 *
 * Copyright 2014 Karol Bucek.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.jruby.ext.openssl;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Locale;

import org.jcodings.specific.UTF8Encoding;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.jruby.Ruby;
import org.jruby.RubyEncoding;
import org.jruby.RubyFile;
import org.jruby.RubyIO;
import org.jruby.RubyString;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;

import org.jruby.ext.openssl.x509store.PEMInputOutput;

/**
 *
 * @author kares
 */
abstract class StringHelper {

    static RubyString newString(final Ruby runtime, final byte[] bytes) {
        final ByteList byteList = new ByteList(bytes, false);
        return RubyString.newString(runtime, byteList);
    }

    static RubyString newString(final Ruby runtime, final byte[] bytes, final int count) {
        final ByteList byteList = new ByteList(bytes, 0, count, false);
        return RubyString.newString(runtime, byteList);
    }

    static ByteList setByteListShared(final RubyString str) {
        try {
            str.setByteListShared();
            return str.getByteList();
        }
        catch (NoSuchMethodError err) { // JRuby 1.6
            RubyString dup = (RubyString) str.dup();
            return dup.getByteList();
        }
    }

    static RubyString newUTF8String(final Ruby runtime, final ByteList bytes) {
        ByteList byteList = new ByteList(RubyEncoding.encodeUTF8(bytes), UTF8Encoding.INSTANCE, false);
        return new RubyString(runtime, runtime.getString(), byteList);
    }

    static RubyString newUTF8String(final Ruby runtime, final CharSequence chars) {
        ByteList byteList = new ByteList(RubyEncoding.encodeUTF8(chars), UTF8Encoding.INSTANCE, false);
        return new RubyString(runtime, runtime.getString(), byteList);
    }

    static RubyString newStringFrozen(final Ruby runtime, final ByteList bytes) {
        final RubyString str = RubyString.newStringShared(runtime, bytes);
        str.setFrozen(true); return str;
    }

    static RubyString newStringFrozen(final Ruby runtime, final CharSequence chars) {
        final RubyString str = RubyString.newString(runtime, chars);
        str.setFrozen(true); return str;
    }

    static byte[] readX509PEM(final ThreadContext context, IRubyObject arg) {
        final RubyString str = StringHelper.readPossibleDERInput(context, arg);
        final ByteList bytes = str.getByteList();
        return readX509PEM(bytes.unsafeBytes(), bytes.getBegin(), bytes.getRealSize());
    }

    static byte[] readX509PEM(final byte[] bytes, final int offset, final int length) {
        InputStreamReader in = new InputStreamReader(new ByteArrayInputStream(bytes, offset, length));
        try {
            byte[] readBytes = PEMInputOutput.readX509PEM(in);
            if ( readBytes != null ) return readBytes;
        }
        catch (IOException e) {
            // this is not PEM encoded, let's use the default argument
        }
        return bytes;
    }

    static RubyString readPossibleDERInput(final ThreadContext context, final IRubyObject arg) {
        return readInput(context, OpenSSL.to_der_if_possible(context, arg));
    }

    static RubyString readInput(final ThreadContext context, final IRubyObject arg) {
        if ( arg instanceof RubyIO ) {
            final IRubyObject result;
            if ( arg instanceof RubyFile ) {
                result = ( (RubyFile) arg.dup() ).read(context);
            }
            else {
                result = ( (RubyIO) arg ).read(context);
            }
            if ( result instanceof RubyString ) return (RubyString) result;
            throw context.runtime.newArgumentError("IO `" + arg.inspect() + "' contained no data");
        }
        return arg.asString();
    }

    static final ByteList NEW_LINE = new ByteList(new byte[] { '\n' }, false);
    static final ByteList COMMA_SPACE = new ByteList(new byte[] { ',',' ' }, false);

    static void gsub(final Ruby runtime, final ByteList str, final byte match, final byte replace) {
        final int begin = str.getBegin();
        final int slen = str.getRealSize();
        final byte[] bytes = str.getUnsafeBytes();
        for ( int i = begin; i < begin + slen; i++ ) {
            if ( bytes[i] == match ) bytes[i] = replace;
        }
    }

    static final char[] S20 = new char[] {
        ' ',' ',' ',' ',  ' ',' ',' ',' ',
        ' ',' ',' ',' ',  ' ',' ',' ',' ',
        ' ',' ',' ',' ',
    };

    private static final DateTimeFormatter ASN_DATE_NO_ZONE =
        DateTimeFormat.forPattern("MMM dd HH:mm:ss yyyy") // + " zzz"
                      .withLocale(Locale.US)
                      .withZone(DateTimeZone.UTC);

    static StringBuilder appendGMTDateTime(final StringBuilder text, final DateTime time) {
        final String date = ASN_DATE_NO_ZONE.print( time.getMillis() );
        final int len = text.length();
        text.append(date).append(' ').append("GMT");
        if ( date.charAt(4) == '0' ) { // Jul 07 -> Jul  7
            text.setCharAt(len + 4, ' ');
        }
        return text;
    }

    //static StringBuilder lowerHexBytes(final BigInteger bytes) {
    //    return lowerHexBytes(bytes.toByteArray(), 1); // skip the sign bit
    //}

    static StringBuilder lowerHexBytes(final byte[] bytes, final int offset) {
        final int len = bytes.length;
        final StringBuilder hex = new StringBuilder(len * 3);
        for (int i = offset; i < bytes.length; i++ ) {
            final String h = Integer.toHexString( bytes[i] & 0xFF );
            if ( h.length() == 1 ) hex.append('0');
            hex.append( h ).append(':');
        }
        if ( hex.length() > 0 ) hex.setLength( hex.length() - 1 );
        return hex;
    }

    static void appendLowerHexValue(final StringBuilder text, final byte[] hex,
        final int indent, final int rowLength) {
        final StringBuilder hexStr = lowerHexBytes( hex, 0 );
        final int len = hexStr.length(); int left = len;
        while ( left > 0 ) {
            int print = rowLength; if ( left < rowLength ) print = left;
            final int start = len - left;
            text.append(S20,0,indent).append( hexStr, start, start + print ).append('\n');
            left -= print;
        }
    }

    @SuppressWarnings("unchecked")
    static <T extends CharSequence> ArrayList<T> split(final T string, final char separator) {
        final ArrayList<T> split = new ArrayList<T>(8); int last = 0;
        for ( int i = 0; i < string.length(); i++ ) {
            if ( string.charAt(i) == separator ) {
                split.add( (T) string.subSequence(last, i) ); last = ++i;
            }
        }
        if ( last == 0 ) split.add(string); // split.isEmpty
        else split.add( (T) string.subSequence(last, string.length()) );
        return split;
    }

    public static String[] split(final String string, final char separator) {
        final ArrayList<CharSequence> split = split((CharSequence) string, separator);
        return split.toArray( new String[ split.size() ] );
    }

}
