/*
\ * Copyright 2015 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.plugin.thrift;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.EnumMap;

import com.navercorp.pinpoint.common.Charsets;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TField;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.protocol.TProtocolException;
import org.apache.thrift.protocol.TType;

/**
 * @author HyunGil Jeong
 */
public class ThriftRequestProperty {

    private EnumMap<ThriftHeader, Object> thriftHeaders = new EnumMap<ThriftHeader, Object>(ThriftHeader.class);
    
    // TRACE_ID
    
    public String getTraceId() {
        return this.getTraceId(null);
    }
    
    public String getTraceId(String defaultValue) {
        if (this.thriftHeaders.containsKey(ThriftHeader.THRIFT_TRACE_ID)) {
            return (String)this.thriftHeaders.get(ThriftHeader.THRIFT_TRACE_ID);
        }
        return defaultValue;
    }
    
    public void setTraceId(String traceId) {
        this.thriftHeaders.put(ThriftHeader.THRIFT_TRACE_ID, traceId);
    }
    
    // SPAN_ID
    
    public Long getSpanId() {
        return this.getSpanId(null);
    }
    
    public Long getSpanId(Long defaultValue) {
        if (this.thriftHeaders.containsKey(ThriftHeader.THRIFT_SPAN_ID)) {
            return (Long)this.thriftHeaders.get(ThriftHeader.THRIFT_SPAN_ID);
        }
        return defaultValue;
    }
    
    public void setSpanId(Long spanId) {
        this.thriftHeaders.put(ThriftHeader.THRIFT_SPAN_ID, spanId);
    }
    
    // PARENT_SPAN_ID
    
    public Long getParentSpanId() {
        return this.getParentSpanId(null);
    }
    
    public Long getParentSpanId(Long defaultValue) {
        if (this.thriftHeaders.containsKey(ThriftHeader.THRIFT_PARENT_SPAN_ID)) {
            return (Long)this.thriftHeaders.get(ThriftHeader.THRIFT_PARENT_SPAN_ID);
        }
        return defaultValue;
    }
    
    public void setParentSpanId(Long parentSpanId) {
        this.thriftHeaders.put(ThriftHeader.THRIFT_PARENT_SPAN_ID, parentSpanId);
    }

    // SAMPLED
    
    public Boolean shouldSample() {
        return this.shouldSample(null);
    }
    
    public Boolean shouldSample(Boolean defaultValue) {
        if (this.thriftHeaders.containsKey(ThriftHeader.THRFIT_SAMPLED)) {
            return (Boolean)this.thriftHeaders.get(ThriftHeader.THRFIT_SAMPLED);
        }
        return defaultValue;
    }
    
    public void setShouldSample(Boolean shouldSample) {
        this.thriftHeaders.put(ThriftHeader.THRFIT_SAMPLED, shouldSample);
    }
    
    // FLAGS
    
    public Short getFlags() {
        return this.getFlags(null);
    }
    
    public Short getFlags(Short defaultValue) {
        if (this.thriftHeaders.containsKey(ThriftHeader.THRIFT_FLAGS)) {
            return (Short)this.thriftHeaders.get(ThriftHeader.THRIFT_FLAGS);
        }
        return defaultValue;
    }
    
    public void setFlags(Short flags) {
        this.thriftHeaders.put(ThriftHeader.THRIFT_FLAGS, flags);
    }
    
    // PARENT_APPLICATION_NAME
    
    public String getParentApplicationName() {
        return this.getParentApplicationName(null);
    }
    
    public String getParentApplicationName(String defaultValue) {
        if (this.thriftHeaders.containsKey(ThriftHeader.THRIFT_PARENT_APPLICATION_NAME)) {
            return (String)this.thriftHeaders.get(ThriftHeader.THRIFT_PARENT_APPLICATION_NAME);
        }
        return defaultValue;
    }
    
    public void setParentApplicationName(String parentApplicationName) {
        this.thriftHeaders.put(ThriftHeader.THRIFT_PARENT_APPLICATION_NAME, parentApplicationName);
    }
    
    // PARENT_APPLICATION_TYPE
    
    public Short getParentApplicationType() {
        return this.getParentApplicationType(null);
    }
    
    public Short getParentApplicationType(Short defaultValue) {
        if (this.thriftHeaders.containsKey(ThriftHeader.THRIFT_PARENT_APPLICATION_TYPE)) {
            return (Short)this.thriftHeaders.get(ThriftHeader.THRIFT_PARENT_APPLICATION_TYPE);
        }
        return defaultValue;
    }
    
    public void setParentApplicationType(Short parentApplicationType) {
        this.thriftHeaders.put(ThriftHeader.THRIFT_PARENT_APPLICATION_TYPE, parentApplicationType);
    }
    
    // ACCEPTOR_HOST
    
    public String getAcceptorHost() {
        return getAcceptorHost(ThriftConstants.UNKNOWN_ADDRESS);
    }
    
    public String getAcceptorHost(String acceptorHost) {
        if (this.thriftHeaders.containsKey(ThriftHeader.THRIFT_HOST)) {
            return (String)this.thriftHeaders.get(ThriftHeader.THRIFT_HOST);
        }
        return acceptorHost;
    }
    
    public void setAcceptorHost(String acceptorHost) {
        this.thriftHeaders.put(ThriftHeader.THRIFT_HOST, acceptorHost);
    }
    
    public void setTraceHeader(ThriftHeader headerKey, Object value) throws TException {
        byte headerType = headerKey.getType();
        if (headerType == TType.STRING) {
            // skipped Strings are read as byte buffer.
            // see org.apache.thrift.protocol.TProtocolUtil.skip(TProtocol, byte, int)
            this.thriftHeaders.put(headerKey, byteBufferToString((ByteBuffer)value));
        } else if (headerType == TType.I64) {
            this.thriftHeaders.put(headerKey, (Long)value);
        } else if (headerType == TType.I16) {
            this.thriftHeaders.put(headerKey, (Short)value);
        } else if (headerType == TType.BOOL) {
            this.thriftHeaders.put(headerKey, (Boolean)value);
        } else {
            throw new TProtocolException("Invalid pinpoint header type - " + headerType);
        }
    }
    
    public void writeTraceHeader(ThriftHeader headerKey, TProtocol oprot) throws TException {
        Object headerValue = this.thriftHeaders.get(headerKey);
        if (headerValue == null) {
            return;
        }
        byte headerType = headerKey.getType();
        TField traceField = new TField(headerKey.name(), headerKey.getType(), headerKey.getId());
        oprot.writeFieldBegin(traceField);
        try {
            if (headerType == TType.STRING) {
                // these will be read as byte buffer although it's probably safe to just use writeString here.
                // see org.apache.thrift.protocol.TProtocolUtil.skip(TProtocol, byte, int)
                oprot.writeBinary(stringToByteBuffer((String)headerValue));
            } else if (headerType == TType.I64) {
                oprot.writeI64((Long)headerValue);
            } else if (headerType == TType.I16) {
                oprot.writeI16((Short)headerValue);
            } else if (headerType == TType.BOOL) {
                oprot.writeBool((Boolean)headerValue);
            } else {
                throw new TProtocolException("Invalid pinpoint header type - " + headerType);
            }
        } finally {
            oprot.writeFieldEnd();
        }
    }
    
    private static final Charset HEADER_CHARSET_ENCODING = Charsets.UTF_8;
    
    private static ByteBuffer stringToByteBuffer(String s) {
        return ByteBuffer.wrap(s.getBytes(HEADER_CHARSET_ENCODING));
    }
    
    private static String byteBufferToString(ByteBuffer buf) {
        CharBuffer charBuffer = HEADER_CHARSET_ENCODING.decode(buf);
        return charBuffer.toString();
    }
    
    @Override
    public String toString() {
        return this.thriftHeaders.toString();
    }
    
}