/*
 * Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.management.counter.perf;

import sun.management.counter.*;
import java.nio.*;

import static java.nio.charset.StandardCharsets.UTF_8;

class PerfDataEntry {
    private class EntryFieldOffset {
        private static final int SIZEOF_BYTE = 1;
        private static final int SIZEOF_INT  = 4;
        private static final int SIZEOF_LONG = 8;

        private static final int ENTRY_LENGTH_SIZE    = SIZEOF_INT;
        private static final int NAME_OFFSET_SIZE     = SIZEOF_INT;
        private static final int VECTOR_LENGTH_SIZE   = SIZEOF_INT;
        private static final int DATA_TYPE_SIZE       = SIZEOF_BYTE;
        private static final int FLAGS_SIZE           = SIZEOF_BYTE;
        private static final int DATA_UNIT_SIZE       = SIZEOF_BYTE;
        private static final int DATA_VAR_SIZE        = SIZEOF_BYTE;
        private static final int DATA_OFFSET_SIZE     = SIZEOF_INT;

        static final int ENTRY_LENGTH  = 0;
        static final int NAME_OFFSET   = ENTRY_LENGTH + ENTRY_LENGTH_SIZE;
        static final int VECTOR_LENGTH = NAME_OFFSET + NAME_OFFSET_SIZE;;
        static final int DATA_TYPE     = VECTOR_LENGTH + VECTOR_LENGTH_SIZE;
        static final int FLAGS         = DATA_TYPE + DATA_TYPE_SIZE;
        static final int DATA_UNIT     = FLAGS + FLAGS_SIZE;
        static final int DATA_VAR      = DATA_UNIT + DATA_UNIT_SIZE;
        static final int DATA_OFFSET   = DATA_VAR + DATA_VAR_SIZE;
    }

    private String       name;
    private int          entryStart;
    private int          entryLength;
    private int          vectorLength;
    private PerfDataType dataType;
    private int          flags;
    private Units        unit;
    private Variability  variability;
    private int          dataOffset;
    private int          dataSize;
    private ByteBuffer   data;

    PerfDataEntry(ByteBuffer b) {
        entryStart = b.position();
        entryLength = b.getInt();

        // check for valid entry length
        if (entryLength <= 0 || entryLength > b.limit()) {
            throw new InstrumentationException("Invalid entry length: " +
                                               " entryLength = " + entryLength);
        }
        // check if last entry occurs before the eof.
        if ((entryStart + entryLength) > b.limit()) {
            throw new InstrumentationException("Entry extends beyond end of buffer: " +
                                               " entryStart = " + entryStart +
                                               " entryLength = " + entryLength +
                                               " buffer limit = " + b.limit());
        }

        b.position(entryStart + EntryFieldOffset.NAME_OFFSET);
        int nameOffset = b.getInt();

        if ((entryStart + nameOffset) > b.limit()) {
            throw new InstrumentationException("Invalid name offset: " +
                                               " entryStart = " + entryStart +
                                               " nameOffset = " + nameOffset +
                                               " buffer limit = " + b.limit());
        }


        b.position(entryStart + EntryFieldOffset.VECTOR_LENGTH);
        vectorLength = b.getInt();

        b.position(entryStart + EntryFieldOffset.DATA_TYPE);
        dataType = PerfDataType.toPerfDataType(b.get());

        b.position(entryStart + EntryFieldOffset.FLAGS);
        flags = b.get();

        b.position(entryStart + EntryFieldOffset.DATA_UNIT);
        unit = Units.toUnits(b.get());

        b.position(entryStart + EntryFieldOffset.DATA_VAR);
        variability = Variability.toVariability(b.get());

        b.position(entryStart + EntryFieldOffset.DATA_OFFSET);
        dataOffset = b.getInt();

        // read in the perfData item name, casting bytes to chars. skip the
        // null terminator
        b.position(entryStart + nameOffset);
        // calculate the length of the name
        int nameLength = 0;
        byte c;
        for (; (c = b.get()) != (byte)0; nameLength++);

        byte[] symbolBytes = new byte[nameLength];
        b.position(entryStart + nameOffset);
        for (int i = 0; i < nameLength; i++) {
            symbolBytes[i] = b.get();
        }

        // convert name into a String
        name = new String(symbolBytes, UTF_8);

        if (variability == Variability.INVALID) {
            throw new InstrumentationException("Invalid variability attribute:" +
                                               " name = " + name);
        }
        if (unit == Units.INVALID) {
            throw new InstrumentationException("Invalid units attribute: " +
                                               " name = " + name);
        }

        if (vectorLength > 0) {
            dataSize = vectorLength * dataType.size();
        } else {
            dataSize = dataType.size();
        }

        // check if data beyond the eof.
        if ((entryStart + dataOffset + dataSize) > b.limit()) {
            throw new InstrumentationException("Data extends beyond end of buffer: " +
                                               " entryStart = " + entryStart +
                                               " dataOffset = " + dataOffset+
                                               " dataSize = " + dataSize +
                                               " buffer limit = " + b.limit());
        }
        // Construct a ByteBuffer for the data
        b.position(entryStart + dataOffset);
        data = b.slice();
        data.order(b.order());
        data.limit(dataSize);
    }


    public int size() {
        return entryLength;
    }

    public String name() {
        return name;
    }

    public PerfDataType type() {
        return dataType;
    }

    public Units units() {
        return unit;
    }

    public int flags() {
        return flags;
    }

    /**
     * Returns the number of elements in the data.
     */
    public int vectorLength() {
        return vectorLength;
    }

    public Variability variability() {
        return variability;
    }

    public ByteBuffer byteData() {
        data.position(0);
        assert data.remaining() == vectorLength();
        return data.duplicate();
    }

    public LongBuffer longData() {
        LongBuffer lb = data.asLongBuffer();
        return lb;
    }
}
