/*
 * Copyright (C) 2007 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;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * Handle thread status updates.
 */
final class HandleNativeHeap extends ChunkHandler {

    public static final int CHUNK_NHGT = type("NHGT"); //$NON-NLS-1$
    public static final int CHUNK_NHSG = type("NHSG"); //$NON-NLS-1$
    public static final int CHUNK_NHST = type("NHST"); //$NON-NLS-1$
    public static final int CHUNK_NHEN = type("NHEN"); //$NON-NLS-1$

    private static final HandleNativeHeap mInst = new HandleNativeHeap();

    /**
     * Handle getting different sized size_t and pointer reads.
     */
    abstract class NativeBuffer {
        public NativeBuffer(ByteBuffer buffer) {
            mBuffer = buffer;
        }

        public abstract int getSizeT();
        public abstract long getPtr();

        protected ByteBuffer mBuffer;
    }

    /**
     * This class treats size_t and pointer values as 32 bit.
     */
    final class NativeBuffer32 extends NativeBuffer {
        public NativeBuffer32(ByteBuffer buffer) {
          super(buffer);
        }

        @Override
        public int getSizeT() {
            return mBuffer.getInt();
        }
        @Override
        public long getPtr() {
            return (long)mBuffer.getInt() & 0x00000000ffffffffL;
        }
    }

    /**
     * This class treats size_t and pointer values as 64 bit.
     */
    final class NativeBuffer64 extends NativeBuffer {
        public NativeBuffer64(ByteBuffer buffer) {
          super(buffer);
        }

        @Override
        public int getSizeT() {
            return (int)mBuffer.getLong();
        }
        @Override
        public long getPtr() {
            return mBuffer.getLong();
        }
    }

    private HandleNativeHeap() {
    }


    /**
     * Register for the packets we expect to get from the client.
     */
    public static void register(MonitorThread mt) {
        mt.registerChunkHandler(CHUNK_NHGT, mInst);
        mt.registerChunkHandler(CHUNK_NHSG, mInst);
        mt.registerChunkHandler(CHUNK_NHST, mInst);
        mt.registerChunkHandler(CHUNK_NHEN, mInst);
    }

    /**
     * Client is ready.
     */
    @Override
    public void clientReady(Client client) throws IOException {}

    /**
     * Client went away.
     */
    @Override
    public void clientDisconnected(Client client) {}

    /**
     * Chunk handler entry point.
     */
    @Override
    public void handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId) {

        Log.d("ddm-nativeheap", "handling " + ChunkHandler.name(type));

        if (type == CHUNK_NHGT) {
            handleNHGT(client, data);
        } else if (type == CHUNK_NHST) {
            // start chunk before any NHSG chunk(s)
            client.getClientData().getNativeHeapData().clearHeapData();
        } else if (type == CHUNK_NHEN) {
            // end chunk after NHSG chunk(s)
            client.getClientData().getNativeHeapData().sealHeapData();
        } else if (type == CHUNK_NHSG) {
            handleNHSG(client, data);
        } else {
            handleUnknownChunk(client, type, data, isReply, msgId);
        }

        client.update(Client.CHANGE_NATIVE_HEAP_DATA);
    }

    /**
     * Send an NHGT (Native Thread GeT) request to the client.
     */
    public static void sendNHGT(Client client) throws IOException {

        ByteBuffer rawBuf = allocBuffer(0);
        JdwpPacket packet = new JdwpPacket(rawBuf);
        ByteBuffer buf = getChunkDataBuf(rawBuf);

        // no data in request message

        finishChunkPacket(packet, CHUNK_NHGT, buf.position());
        Log.d("ddm-nativeheap", "Sending " + name(CHUNK_NHGT));
        client.send(packet, mInst);

        rawBuf = allocBuffer(2);
        packet = new JdwpPacket(rawBuf);
        buf = getChunkDataBuf(rawBuf);

        buf.put((byte)HandleHeap.WHEN_DISABLE);
        buf.put((byte)HandleHeap.WHAT_OBJ);

        finishChunkPacket(packet, CHUNK_NHSG, buf.position());
        Log.d("ddm-nativeheap", "Sending " + name(CHUNK_NHSG));
        client.send(packet, mInst);
    }

    /*
     * Handle our native heap data.
     */
    private void handleNHGT(Client client, ByteBuffer data) {
        ClientData clientData = client.getClientData();

        Log.d("ddm-nativeheap", "NHGT: " + data.limit() + " bytes");

        data.order(ByteOrder.LITTLE_ENDIAN);

        // There are two supported header formats.
        //
        // The original version of the header for 32 bit processes:
        //
        //   uint32_t mapSize;
        //   uint32_t mapSize;
        //   uint32_t allocSize;
        //   uint32_t allocInfoSize;
        //   uint32_t totalMemory;
        //   uint32_t backtrace_size;
        //
        // The new header which includes a signature and pointer size:
        //
        //   uint32_t signature;   (Which is always 0x812345dd)
        //   uint16_t version;     (Only version 2 of the new format supported)
        //   uint16_t pointerSize; (Size in bytes of size_t/pointer values)
        //   size_t mapSize;
        //   size_t allocSize;
        //   size_t allocInfoSize;
        //   size_t totalMemory;
        //   size_t backtrace_size;
        //
        // If the signature doesn't match, then the code uses the original
        // header format. If the signature matches, then use the new
        // header format with variable sizes of size_t and pointers.
        int signature = data.getInt(0);
        short pointerSize = 4;
        if (signature == 0x812345dd) {
            // Consume signature value.
            int ignore = data.getInt();
            short version = data.getShort();
            if (version != 2) {
                Log.e("ddms", "Unknown header version: " + version);
                return;
            }
            pointerSize = data.getShort();
        }
        NativeBuffer buffer;
        if (pointerSize == 4) {
            buffer = new NativeBuffer32(data);
        } else if (pointerSize == 8) {
            buffer = new NativeBuffer64(data);
        } else {
            Log.e("ddms", "Unknown pointer size: " + pointerSize);
            return;
        }

        // clear the previous run
        clientData.clearNativeAllocationInfo();

        int mapSize = buffer.getSizeT();
        int allocSize = buffer.getSizeT();
        int allocInfoSize = buffer.getSizeT();
        int totalMemory = buffer.getSizeT();
        int backtraceSize = buffer.getSizeT();

        Log.d("ddms", "mapSize: " + mapSize);
        Log.d("ddms", "allocSize: " + allocSize);
        Log.d("ddms", "allocInfoSize: " + allocInfoSize);
        Log.d("ddms", "totalMemory: " + totalMemory);

        clientData.setTotalNativeMemory(totalMemory);

        // this means that updates aren't turned on.
        if (allocInfoSize == 0) {
          return;
        }

        if (mapSize > 0) {
            byte[] maps = new byte[mapSize];
            data.get(maps, 0, mapSize);
            parseMaps(clientData, maps);
        }

        int iterations = allocSize / allocInfoSize;
        for (int i = 0 ; i < iterations ; i++) {
            NativeAllocationInfo info = new NativeAllocationInfo(
                    buffer.getSizeT() /* size */,
                    buffer.getSizeT() /* allocations */);

            for (int j = 0 ; j < backtraceSize ; j++) {
                long addr = buffer.getPtr();
                if (addr == 0x0) {
                    // skip past null addresses
                    continue;
                }

                info.addStackCallAddress(addr);
            }
            clientData.addNativeAllocation(info);
        }
    }

    private void handleNHSG(Client client, ByteBuffer data) {
        byte dataCopy[] = new byte[data.limit()];
        data.rewind();
        data.get(dataCopy);
        data = ByteBuffer.wrap(dataCopy);
        client.getClientData().getNativeHeapData().addHeapData(data);

        if (true) {
            return;
        }

        byte[] copy = new byte[data.limit()];
        data.get(copy);

        ByteBuffer buffer = ByteBuffer.wrap(copy);
        buffer.order(ByteOrder.BIG_ENDIAN);

        int id = buffer.getInt();
        int unitsize = buffer.get();
        long startAddress = buffer.getInt() & 0x00000000ffffffffL;
        int offset = buffer.getInt();
        int allocationUnitCount = buffer.getInt();

        // read the usage
        while (buffer.position() < buffer.limit()) {
            int eState = buffer.get() & 0x000000ff;
            int eLen = (buffer.get() & 0x000000ff) + 1;
        }
    }

    private void parseMaps(ClientData clientData, byte[] maps) {
        InputStreamReader input = new InputStreamReader(new ByteArrayInputStream(maps));
        BufferedReader reader = new BufferedReader(input);

        String line;

        try {
            while ((line = reader.readLine()) != null) {
                Log.d("ddms", "line: " + line);
                // Expected format:
                //   7fe51f2000-7fe5213000 rw-p 00000000 00:00 0      [stack]

                int library_start = line.lastIndexOf(' ');
                if (library_start == -1) {
                    continue;
                }

                // Assume that any string that starts with a / is a
                // shared library or executable that we will try to symbolize.
                String library = line.substring(library_start+1);
                if (!library.startsWith("/")) {
                    continue;
                }

                // Parse the start and end address range.
                int dashIndex = line.indexOf('-');
                int spaceIndex = line.indexOf(' ', dashIndex);
                if (dashIndex == -1 || spaceIndex == -1) {
                    continue;
                }

                long startAddr = 0;
                long endAddr = 0;
                try {
                    startAddr = Long.parseLong(line.substring(0, dashIndex), 16);
                    endAddr = Long.parseLong(line.substring(dashIndex+1, spaceIndex), 16);
                } catch (NumberFormatException e) {
                    e.printStackTrace();
                    continue;
                }

                clientData.addNativeLibraryMapInfo(startAddr, endAddr, library);
                Log.d("ddms", library + "(" + Long.toHexString(startAddr) +
                      " - " + Long.toHexString(endAddr) + ")");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


}

