/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * 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.android.ddmlib.log;

import com.android.ddmlib.log.EventValueDescription.ValueType;
import com.android.ddmlib.log.LogReceiver.LogEntry;

/**
 * Custom Event Container for the Gc event since this event doesn't simply output data in
 * int or long format, but encodes several values on 4 longs.
 * <p>
 * The array of {@link EventValueDescription}s parsed from the "event-log-tags" file must
 * be ignored, and instead, the array returned from {@link #getValueDescriptions()} must be used. 
 */
final class GcEventContainer extends EventContainer {
    
    public static final int GC_EVENT_TAG = 20001;

    private String processId;
    private long gcTime;
    private long bytesFreed;
    private long objectsFreed;
    private long actualSize;
    private long allowedSize;
    private long softLimit;
    private long objectsAllocated;
    private long bytesAllocated;
    private long zActualSize;
    private long zAllowedSize;
    private long zObjectsAllocated;
    private long zBytesAllocated;
    private long dlmallocFootprint;
    private long mallinfoTotalAllocatedSpace;
    private long externalLimit;
    private long externalBytesAllocated;

    GcEventContainer(LogEntry entry, int tag, Object data) {
        super(entry, tag, data);
        init(data);
    }

    GcEventContainer(int tag, int pid, int tid, int sec, int nsec, Object data) {
        super(tag, pid, tid, sec, nsec, data);
        init(data);
    }

    /**
     * @param data
     */
    private void init(Object data) {
        if (data instanceof Object[]) {
            Object[] values = (Object[])data;
            for (int i = 0; i < values.length; i++) {
                if (values[i] instanceof Long) {
                    parseDvmHeapInfo((Long)values[i], i);
                }
            }
        }
    }
    
    @Override
    public EventValueType getType() {
        return EventValueType.LIST;
    }

    @Override
    public boolean testValue(int index, Object value, CompareMethod compareMethod)
            throws InvalidTypeException {
        // do a quick easy check on the type.
        if (index == 0) {
            if (!(value instanceof String)) {
                throw new InvalidTypeException();
            }
        } else if (!(value instanceof Long)) {
            throw new InvalidTypeException();
        }
        
        switch (compareMethod) {
            case EQUAL_TO:
                if (index == 0) {
                    return processId.equals(value);
                } else {
                    return getValueAsLong(index) == (Long) value;
                }
            case LESSER_THAN:
                return getValueAsLong(index) <= (Long) value;
            case LESSER_THAN_STRICT:
                return getValueAsLong(index) < (Long) value;
            case GREATER_THAN:
                return getValueAsLong(index) >= (Long) value;
            case GREATER_THAN_STRICT:
                return getValueAsLong(index) > (Long) value;
            case BIT_CHECK:
                return (getValueAsLong(index) & (Long) value) != 0;
        }

        throw new ArrayIndexOutOfBoundsException();
    }

    @Override
    public Object getValue(int valueIndex) {
        if (valueIndex == 0) {
            return processId;
        }
        
        try {
            return getValueAsLong(valueIndex);
        } catch (InvalidTypeException e) {
            // this would only happened if valueIndex was 0, which we test above.
        }
        
        return null;
    }

    @Override
    public double getValueAsDouble(int valueIndex) throws InvalidTypeException {
        return (double)getValueAsLong(valueIndex);
    }

    @Override
    public String getValueAsString(int valueIndex) {
        switch (valueIndex) {
            case 0:
                return processId;
            default:
                try {
                    return Long.toString(getValueAsLong(valueIndex));
                } catch (InvalidTypeException e) {
                    // we shouldn't stop there since we test, in this method first.
                }
        }

        throw new ArrayIndexOutOfBoundsException();
    }
    
    /**
     * Returns a custom array of {@link EventValueDescription} since the actual content of this
     * event (list of (long, long) does not match the values encoded into those longs.
     */
    static EventValueDescription[] getValueDescriptions() {
        try {
            return new EventValueDescription[] {
                    new EventValueDescription("Process Name", EventValueType.STRING),
                    new EventValueDescription("GC Time", EventValueType.LONG,
                            ValueType.MILLISECONDS),
                    new EventValueDescription("Freed Objects", EventValueType.LONG,
                            ValueType.OBJECTS),
                    new EventValueDescription("Freed Bytes", EventValueType.LONG, ValueType.BYTES),
                    new EventValueDescription("Soft Limit", EventValueType.LONG, ValueType.BYTES),
                    new EventValueDescription("Actual Size (aggregate)", EventValueType.LONG,
                            ValueType.BYTES),
                    new EventValueDescription("Allowed Size (aggregate)", EventValueType.LONG,
                            ValueType.BYTES),
                    new EventValueDescription("Allocated Objects (aggregate)",
                            EventValueType.LONG, ValueType.OBJECTS),
                    new EventValueDescription("Allocated Bytes (aggregate)", EventValueType.LONG,
                            ValueType.BYTES),
                    new EventValueDescription("Actual Size", EventValueType.LONG, ValueType.BYTES),
                    new EventValueDescription("Allowed Size", EventValueType.LONG, ValueType.BYTES),
                    new EventValueDescription("Allocated Objects", EventValueType.LONG,
                            ValueType.OBJECTS),
                    new EventValueDescription("Allocated Bytes", EventValueType.LONG,
                            ValueType.BYTES),
                    new EventValueDescription("Actual Size (zygote)", EventValueType.LONG,
                            ValueType.BYTES),
                    new EventValueDescription("Allowed Size (zygote)", EventValueType.LONG,
                            ValueType.BYTES),
                    new EventValueDescription("Allocated Objects (zygote)", EventValueType.LONG,
                            ValueType.OBJECTS),
                    new EventValueDescription("Allocated Bytes (zygote)", EventValueType.LONG,
                            ValueType.BYTES),
                    new EventValueDescription("External Allocation Limit", EventValueType.LONG,
                            ValueType.BYTES),
                    new EventValueDescription("External Bytes Allocated", EventValueType.LONG,
                            ValueType.BYTES),
                    new EventValueDescription("dlmalloc Footprint", EventValueType.LONG,
                            ValueType.BYTES),
                    new EventValueDescription("Malloc Info: Total Allocated Space",
                            EventValueType.LONG, ValueType.BYTES),
                  };
        } catch (InvalidValueTypeException e) {
            // this shouldn't happen since we control manual the EventValueType and the ValueType
            // values. For development purpose, we assert if this happens.
            assert false;
        }

        // this shouldn't happen, but the compiler complains otherwise.
        return null;
    }

    private void parseDvmHeapInfo(long data, int index) {
        switch (index) {
            case 0:
                //    [63   ] Must be zero
                //    [62-24] ASCII process identifier
                //    [23-12] GC time in ms
                //    [11- 0] Bytes freed
                
                gcTime = float12ToInt((int)((data >> 12) & 0xFFFL));
                bytesFreed = float12ToInt((int)(data & 0xFFFL));
                
                // convert the long into an array, in the proper order so that we can convert the
                // first 5 char into a string.
                byte[] dataArray = new byte[8];
                put64bitsToArray(data, dataArray, 0);
                
                // get the name from the string
                processId = new String(dataArray, 0, 5);
                break;
            case 1:
                //    [63-62] 10
                //    [61-60] Reserved; must be zero
                //    [59-48] Objects freed
                //    [47-36] Actual size (current footprint)
                //    [35-24] Allowed size (current hard max)
                //    [23-12] Objects allocated
                //    [11- 0] Bytes allocated
                objectsFreed = float12ToInt((int)((data >> 48) & 0xFFFL));
                actualSize = float12ToInt((int)((data >> 36) & 0xFFFL));
                allowedSize = float12ToInt((int)((data >> 24) & 0xFFFL));
                objectsAllocated = float12ToInt((int)((data >> 12) & 0xFFFL));
                bytesAllocated = float12ToInt((int)(data & 0xFFFL));
                break;
            case 2:
                //    [63-62] 11
                //    [61-60] Reserved; must be zero
                //    [59-48] Soft limit (current soft max)
                //    [47-36] Actual size (current footprint)
                //    [35-24] Allowed size (current hard max)
                //    [23-12] Objects allocated
                //    [11- 0] Bytes allocated
                softLimit = float12ToInt((int)((data >> 48) & 0xFFFL));
                zActualSize = float12ToInt((int)((data >> 36) & 0xFFFL));
                zAllowedSize = float12ToInt((int)((data >> 24) & 0xFFFL));
                zObjectsAllocated = float12ToInt((int)((data >> 12) & 0xFFFL));
                zBytesAllocated = float12ToInt((int)(data & 0xFFFL));
                break;
            case 3:
                //    [63-48] Reserved; must be zero
                //    [47-36] dlmallocFootprint
                //    [35-24] mallinfo: total allocated space
                //    [23-12] External byte limit
                //    [11- 0] External bytes allocated
                dlmallocFootprint = float12ToInt((int)((data >> 36) & 0xFFFL));
                mallinfoTotalAllocatedSpace = float12ToInt((int)((data >> 24) & 0xFFFL));
                externalLimit = float12ToInt((int)((data >> 12) & 0xFFFL));
                externalBytesAllocated = float12ToInt((int)(data & 0xFFFL));
                break;
            default:
                break;
        }
    }
    
    /**
     * Converts a 12 bit float representation into an unsigned int (returned as a long)
     * @param f12
     */
    private static long float12ToInt(int f12) {
        return (f12 & 0x1FF) << ((f12 >>> 9) * 4);
    }
    
    /**
     * puts an unsigned value in an array.
     * @param value The value to put.
     * @param dest the destination array
     * @param offset the offset in the array where to put the value.
     *      Array length must be at least offset + 8
     */
    private static void put64bitsToArray(long value, byte[] dest, int offset) {
        dest[offset + 7] = (byte)(value & 0x00000000000000FFL);
        dest[offset + 6] = (byte)((value & 0x000000000000FF00L) >> 8);
        dest[offset + 5] = (byte)((value & 0x0000000000FF0000L) >> 16);
        dest[offset + 4] = (byte)((value & 0x00000000FF000000L) >> 24);
        dest[offset + 3] = (byte)((value & 0x000000FF00000000L) >> 32);
        dest[offset + 2] = (byte)((value & 0x0000FF0000000000L) >> 40);
        dest[offset + 1] = (byte)((value & 0x00FF000000000000L) >> 48);
        dest[offset + 0] = (byte)((value & 0xFF00000000000000L) >> 56);
    }
    
    /**
     * Returns the long value of the <code>valueIndex</code>-th value.
     * @param valueIndex the index of the value.
     * @throws InvalidTypeException if index is 0 as it is a string value.
     */
    private long getValueAsLong(int valueIndex) throws InvalidTypeException {
        switch (valueIndex) {
            case 0:
                throw new InvalidTypeException();
            case 1:
                return gcTime;
            case 2:
                return objectsFreed;
            case 3:
                return bytesFreed;
            case 4:
                return softLimit;
            case 5:
                return actualSize;
            case 6:
                return allowedSize;
            case 7:
                return objectsAllocated;
            case 8:
                return bytesAllocated;
            case 9:
                return actualSize - zActualSize;
            case 10:
                return allowedSize - zAllowedSize;
            case 11:
                return objectsAllocated - zObjectsAllocated;
            case 12:
                return bytesAllocated - zBytesAllocated;
            case 13:
               return zActualSize;
            case 14:
                return zAllowedSize;
            case 15:
                return zObjectsAllocated;
            case 16:
                return zBytesAllocated;
            case 17:
                return externalLimit;
            case 18:
                return externalBytesAllocated;
            case 19:
                return dlmallocFootprint;
            case 20:
                return mallinfoTotalAllocatedSpace;
        }

        throw new ArrayIndexOutOfBoundsException();
    }
}
