package com.jscape.inet.http.protocol.marshaling;

import com.jscape.util.ArrayTools;
import com.jscape.util.IOTools;
import com.jscape.util.StringTools;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

/**
* @author Alex Usun
*/
public class ChunkCodec {

    private static final byte[] EOL = {'\r', '\n'};
    private static final int LENGTH_RADIX = 16;

    public static Chunk readValue(InputStream in)
            throws IOException {
        Header header = readHeader(in);

        if (header == null) {
            return null;
        }

        byte[] data = IOTools.read(new byte[header.length], in);
        readTrailingEol(in);

        return new Chunk(data, 0, data.length, header.extensions);
    }

    public static void writeValue(Chunk chunk, OutputStream out)
            throws IOException {
        writeHeader(chunk, out);

        if (chunk.length > 0) {
            out.write(chunk.data, chunk.offset, chunk.length);
            out.write(EOL);
        }
    }

    private static Header readHeader(InputStream in)
            throws IOException {
        byte[] dataBytes = IOTools.readSequence(in, EOL, false);
        
        if (dataBytes == null) {
            return null;
        }
        
        String data = new String(dataBytes, StandardCharsets.US_ASCII);
        String[] elements = StringTools.copyTrimmed(StringTools.split(data, ";", false));
        int value = Integer.parseInt(elements[0], LENGTH_RADIX);
        String[] extensions = elements.length > 1 ? ArrayTools.copy(elements, 1, new String[elements.length - 1]) : new String[0];
        
        return new Header(value, extensions);
    }

    private static void writeHeader(Chunk chunk, OutputStream out)
            throws IOException {
        StringBuilder builder = new StringBuilder(Integer.toString(chunk.length, LENGTH_RADIX));
        for (String extension : chunk.extensions) {
            builder.append(';').append(extension);
        }
        byte[] data = builder.toString().getBytes(StandardCharsets.US_ASCII);
        out.write(data);
        out.write(EOL);
    }

    private static void readTrailingEol(InputStream in)
            throws IOException {
        for (int i = 0; i < EOL.length; ++i) {
            IOTools.read(in);
        }
    }

    private ChunkCodec() {
        // empty
    }
    
    private static class Header {
        
        public final int length;
        public final String[] extensions;
    
        private Header(int length, String... extensions) {
            this.length = length;
            this.extensions = extensions;
        }
    
        @Override
        public String toString() {
            return "Length {"
                   + "value=" + this.length
                   + ", extensions=" + Arrays.toString(this.extensions)
                   + '}';
        }
    }
}
