/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.activemq.command;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.apache.activemq.state.CommandVisitor;
import org.apache.activemq.util.ByteArrayInputStream;
import org.apache.activemq.util.ByteArrayOutputStream;
import org.apache.activemq.util.ByteSequence;
import org.apache.activemq.util.MarshallingSupport;
import org.apache.activemq.wireformat.WireFormat;
import org.fusesource.hawtbuf.UTF8Buffer;

/**
 * @openwire:marshaller code="1"
 *
 */
public class WireFormatInfo implements Command, MarshallAware {

    public static final byte DATA_STRUCTURE_TYPE = CommandTypes.WIREFORMAT_INFO;
    // Max number of properties allowed in the map is 64
    static final int MAX_PROPERTY_SIZE = 64;
    // Used to validate property values that allocate buffers, limit to 512 bytes
    public static final int MAX_PROPERTY_BUFFER_SIZE = 512;
    // Do not allow any nested collections in properties
    static final int MAX_PROPERTY_DEPTH = 0;
    private static final byte[] MAGIC = new byte[] {'A', 'c', 't', 'i', 'v', 'e', 'M', 'Q'};

    protected byte[] magic = MAGIC;
    protected int version;
    protected ByteSequence marshalledProperties;

    protected transient Map<String, Object> properties;
    private transient Endpoint from;
    private transient Endpoint to;

    @Override
    public byte getDataStructureType() {
        return DATA_STRUCTURE_TYPE;
    }

    @Override
    public boolean isWireFormatInfo() {
        return true;
    }

    @Override
    public boolean isMarshallAware() {
        return true;
    }

    /**
     * @openwire:property version=1 size=8 testSize=-1
     */
    public byte[] getMagic() {
        return magic;
    }

    public void setMagic(byte[] magic) {
        this.magic = magic;
    }

    /**
     * @openwire:property version=1
     */
    public int getVersion() {
        return version;
    }

    public void setVersion(int version) {
        this.version = version;
    }

    /**
     * @openwire:property version=1
     */
    public ByteSequence getMarshalledProperties() {
        return marshalledProperties;
    }

    public void setMarshalledProperties(ByteSequence marshalledProperties) {
        this.marshalledProperties = marshalledProperties;
    }

    /**
     * The endpoint within the transport where this message came from.
     */
    @Override
    public Endpoint getFrom() {
        return from;
    }

    @Override
    public void setFrom(Endpoint from) {
        this.from = from;
    }

    /**
     * The endpoint within the transport where this message is going to - null
     * means all endpoints.
     */
    @Override
    public Endpoint getTo() {
        return to;
    }

    @Override
    public void setTo(Endpoint to) {
        this.to = to;
    }

    // ////////////////////
    //
    // Implementation Methods.
    //
    // ////////////////////

    public Object getProperty(String name) throws IOException {
        if (properties == null) {
            if (marshalledProperties == null) {
                return null;
            }
            properties = unmarshalProperties(marshalledProperties);
        }
        return properties.get(name);
    }

    @SuppressWarnings("unchecked")
    public Map<String, Object> getProperties() throws IOException {
        if (properties == null) {
            if (marshalledProperties == null) {
                return Collections.EMPTY_MAP;
            }
            properties = unmarshalProperties(marshalledProperties);
        }
        return Collections.unmodifiableMap(properties);
    }

    public void clearProperties() {
        marshalledProperties = null;
        properties = null;
    }

    public void setProperty(String name, Object value) throws IOException {
        lazyCreateProperties();
        properties.put(name, value);
    }

    protected void lazyCreateProperties() throws IOException {
        if (properties == null) {
            if (marshalledProperties == null) {
                properties = new HashMap<String, Object>();
            } else {
                properties = unmarshalProperties(marshalledProperties);
                marshalledProperties = null;
            }
        }
    }

    private Map<String, Object> unmarshalProperties(ByteSequence marshalledProperties) throws IOException {
        return MarshallingSupport.unmarshalPrimitiveMap(new DataInputStream(new ByteArrayInputStream(marshalledProperties)),
                MAX_PROPERTY_SIZE, MAX_PROPERTY_BUFFER_SIZE, MAX_PROPERTY_DEPTH);
    }

    @Override
    public void beforeMarshall(WireFormat wireFormat) throws IOException {
        // Need to marshal the properties.
        if (marshalledProperties == null && properties != null) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            DataOutputStream os = new DataOutputStream(baos);
            MarshallingSupport.marshalPrimitiveMap(properties, os);
            os.close();
            marshalledProperties = baos.toByteSequence();
        }
    }

    @Override
    public void afterMarshall(WireFormat wireFormat) throws IOException {
    }

    @Override
    public void beforeUnmarshall(WireFormat wireFormat) throws IOException {
    }

    @Override
    public void afterUnmarshall(WireFormat wireFormat) throws IOException {
    }

    public boolean isValid() {
        return magic != null && Arrays.equals(magic, MAGIC);
    }

    @Override
    public void setResponseRequired(boolean responseRequired) {
    }

    /**
     * @throws IOException
     */
    public boolean isCacheEnabled() throws IOException {
        return Boolean.TRUE == getProperty("CacheEnabled");
    }

    public void setCacheEnabled(boolean cacheEnabled) throws IOException {
        setProperty("CacheEnabled", cacheEnabled ? Boolean.TRUE : Boolean.FALSE);
    }

    /**
     * @throws IOException
     */
    public boolean isStackTraceEnabled() throws IOException {
        return Boolean.TRUE == getProperty("StackTraceEnabled");
    }

    public void setStackTraceEnabled(boolean stackTraceEnabled) throws IOException {
        setProperty("StackTraceEnabled", stackTraceEnabled ? Boolean.TRUE : Boolean.FALSE);
    }

    /**
     * @throws IOException
     */
    public boolean isTcpNoDelayEnabled() throws IOException {
        return Boolean.TRUE == getProperty("TcpNoDelayEnabled");
    }

    public void setTcpNoDelayEnabled(boolean tcpNoDelayEnabled) throws IOException {
        setProperty("TcpNoDelayEnabled", tcpNoDelayEnabled ? Boolean.TRUE : Boolean.FALSE);
    }

    /**
     * @throws IOException
     */
    public boolean isSizePrefixDisabled() throws IOException {
        return Boolean.TRUE == getProperty("SizePrefixDisabled");
    }

    public void setSizePrefixDisabled(boolean prefixPacketSize) throws IOException {
        setProperty("SizePrefixDisabled", prefixPacketSize ? Boolean.TRUE : Boolean.FALSE);
    }

    /**
     * @throws IOException
     */
    public boolean isTightEncodingEnabled() throws IOException {
        return Boolean.TRUE == getProperty("TightEncodingEnabled");
    }

    public void setTightEncodingEnabled(boolean tightEncodingEnabled) throws IOException {
        setProperty("TightEncodingEnabled", tightEncodingEnabled ? Boolean.TRUE : Boolean.FALSE);
    }

    public String getHost() throws IOException {
        UTF8Buffer buff = (UTF8Buffer) getProperty("Host");
        if( buff == null ) {
            return null;
        }
        return buff.toString();
    }

    public void setHost(String hostname) throws IOException {
        setProperty("Host", hostname);
    }

    /**
     * @throws IOException
     */
    public long getMaxInactivityDuration() throws IOException {
        Long l = (Long)getProperty("MaxInactivityDuration");
        return l == null ? 0 : l;
    }

    public void setMaxInactivityDuration(long maxInactivityDuration) throws IOException {
        setProperty("MaxInactivityDuration", maxInactivityDuration);
    }

    public long getMaxInactivityDurationInitalDelay() throws IOException {
        Long l = (Long)getProperty("MaxInactivityDurationInitalDelay");
        return l == null ? 0 : l;
    }

    public void setMaxInactivityDurationInitalDelay(long maxInactivityDurationInitalDelay) throws IOException {
        setProperty("MaxInactivityDurationInitalDelay", maxInactivityDurationInitalDelay);
    }

    public long getMaxFrameSize() throws IOException {
        Long l = (Long)getProperty("MaxFrameSize");
        return l == null ? 0 : l;
    }

    public void setMaxFrameSize(long maxFrameSize) throws IOException {
        setProperty("MaxFrameSize", maxFrameSize);
    }

    /**
     * @throws IOException
     */
    public int getCacheSize() throws IOException {
        Integer i = (Integer)getProperty("CacheSize");
        return i == null ? 0 : i;
    }

    public void setCacheSize(int cacheSize) throws IOException {
        setProperty("CacheSize", cacheSize);
    }

    /**
     * @throws IOException
     */
    public String getProviderName() throws IOException {
        Object o = getProperty("ProviderName");
        return o == null ? null : o.toString();
    }

    public void setProviderName(String providerName) throws IOException {
        setProperty("ProviderName", providerName);
    }

    /**
     * @throws IOException
     */
    public String getProviderVersion() throws IOException {
        Object o = getProperty("ProviderVersion");
        return o == null ? null : o.toString();
    }

    public void setProviderVersion(String providerVersion) throws IOException {
        setProperty("ProviderVersion", providerVersion);
    }

    /**
     * @throws IOException
     */
    public String getPlatformDetails() throws IOException {
        Object o = getProperty("PlatformDetails");
        return o == null ? null : o.toString();
    }

    public void setPlatformDetails(String platformDetails) throws IOException {
        setProperty("PlatformDetails", platformDetails);
    }

    public boolean isMaxFrameSizeEnabled() throws IOException {
        return Boolean.TRUE == getProperty("MaxFrameSizeEnabled");
    }

    public void setMaxFrameSizeEnabled(boolean maxFrameSizeEnabled) throws IOException {
        setProperty("MaxFrameSizeEnabled", maxFrameSizeEnabled ? Boolean.TRUE : Boolean.FALSE);
    }

    @Override
    public Response visit(CommandVisitor visitor) throws Exception {
        return visitor.processWireFormat(this);
    }

    @Override
    public String toString() {
        Map<String, Object> p = null;
        try {
            p = getProperties();
        } catch (IOException ignore) {
        }
        return "WireFormatInfo { version=" + version + ", properties=" + p + ", magic=" + toString(magic) + "}";
    }

    private String toString(byte[] data) {
        StringBuilder sb = new StringBuilder();
        sb.append('[');
        for (int i = 0; i < data.length; i++) {
            if (i != 0) {
                sb.append(',');
            }
            sb.append((char)data[i]);
        }
        sb.append(']');
        return sb.toString();
    }

    // /////////////////////////////////////////////////////////////
    //
    // This are not implemented.
    //
    // /////////////////////////////////////////////////////////////

    @Override
    public void setCommandId(int value) {
    }

    @Override
    public int getCommandId() {
        return 0;
    }

    @Override
    public boolean isResponseRequired() {
        return false;
    }

    @Override
    public boolean isResponse() {
        return false;
    }

    @Override
    public boolean isBrokerInfo() {
        return false;
    }

    @Override
    public boolean isMessageDispatch() {
        return false;
    }

    @Override
    public boolean isMessage() {
        return false;
    }

    @Override
    public boolean isMessageAck() {
        return false;
    }

    @Override
    public boolean isMessageDispatchNotification() {
        return false;
    }

    @Override
    public boolean isShutdownInfo() {
        return false;
    }

    @Override
    public boolean isConnectionControl() {
        return false;
    }

    @Override
    public boolean isConsumerControl() {
        return false;
    }

    public void setCachedMarshalledForm(WireFormat wireFormat, ByteSequence data) {
    }

    public ByteSequence getCachedMarshalledForm(WireFormat wireFormat) {
        return null;
    }
}
