/*
 * Copyright 2014 NAVER Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.navercorp.pinpoint.rpc.control;

import com.navercorp.pinpoint.common.Charsets;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * @author koo.taejin
 */
public class ControlMessageDecoder {

    private final Charset charset;

    public ControlMessageDecoder() {
        this.charset = Charsets.UTF_8;
    }

    public Object decode(byte[] in) throws ProtocolException {
        return decode(ByteBuffer.wrap(in));
    }

    public Object decode(ByteBuffer in) throws ProtocolException {
        byte type = in.get();
        switch (type) {
        case ControlMessageProtocolConstant.TYPE_CHARACTER_NULL:
            return null;
        case ControlMessageProtocolConstant.TYPE_CHARACTER_BOOL_TRUE:
            return Boolean.TRUE;
        case ControlMessageProtocolConstant.TYPE_CHARACTER_BOOL_FALSE:
            return Boolean.FALSE;
        case ControlMessageProtocolConstant.TYPE_CHARACTER_INT:
            return in.getInt();
        case ControlMessageProtocolConstant.TYPE_CHARACTER_LONG:
            return in.getLong();
        case ControlMessageProtocolConstant.TYPE_CHARACTER_DOUBLE:
            return Double.longBitsToDouble(in.getLong());
        case ControlMessageProtocolConstant.TYPE_CHARACTER_STRING:
            return decodeString(in);
        case ControlMessageProtocolConstant.CONTROL_CHARACTER_LIST_START:
            List<Object> answerList = new ArrayList<Object>();
            while (!isListFinished(in)) {
                answerList.add(decode(in));
            }
            in.get(); // Skip the terminator
            return answerList;
        case ControlMessageProtocolConstant.CONTROL_CHARACTER_MAP_START:
            Map<Object, Object> answerMap = new LinkedHashMap<Object, Object>();
            while (!isMapFinished(in)) {
                Object key = decode(in);
                Object value = decode(in);
                answerMap.put(key, value);
            }
            in.get(); // Skip the terminator
            return answerMap;
        default:
            throw new ProtocolException("invalid type character: " + (char) type + " (" + "0x" + Integer.toHexString(type) + ")");
        }
    }

    private Object decodeString(ByteBuffer in) {
        int length = readStringLength(in);

        byte[] bytesToEncode = new byte[length];
        in.get(bytesToEncode);

        return new String(bytesToEncode, charset);
    }

    private boolean isMapFinished(ByteBuffer in) {
        return in.get(in.position()) == ControlMessageProtocolConstant.CONTROL_CHARACTER_MAP_END;
    }

    private boolean isListFinished(ByteBuffer in) {
        return in.get(in.position()) == ControlMessageProtocolConstant.CONTROL_CHARACTER_LIST_END;
    }

    private int readStringLength(ByteBuffer in) {
        int result = 0;
        int shift = 0;

        while (true) {
            byte b = in.get();
            result |= (b & 0x7F) << shift;
            if ((b & 0x80) != 128)
                break;
            shift += 7;
        }
        return result;
    }

}
