/**
 * Copyright 2013 JogAmp Community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and should not be interpreted as representing official policies, either expressed
 * or implied, of JogAmp Community.
 */
package jogamp.common.os.elf;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;

import com.jogamp.common.os.Platform.ABIType;
import com.jogamp.common.os.Platform.CPUFamily;
import com.jogamp.common.os.Platform.CPUType;

import static jogamp.common.os.elf.IOUtils.readBytes;
import static jogamp.common.os.elf.IOUtils.seek;
import static jogamp.common.os.elf.IOUtils.shortToInt;
import static jogamp.common.os.elf.IOUtils.toHexString;

/**
 * ELF ABI Header Part-2
 * <p>
 * Part-2 can only be read w/ knowledge of CPUType!
 * </p>
 * <p>
 * References:
 * <ul>
 *   <li>http://www.sco.com/developers/gabi/latest/contents.html</li>
 *   <li>https://en.wikipedia.org/wiki/Executable_and_Linkable_Format</li>
 *   <li>http://linux.die.net/man/5/elf</li>
 *   <li>http://infocenter.arm.com/
 *   <ul>
 *      <li>ARM IHI 0044E, current through ABI release 2.09</li>
 *      <li>ARM IHI 0056B: Elf for ARM 64-bit Architecture</li>
 *   </ul></li>
 * </ul>
 * </p>
 */
public class ElfHeaderPart2 {
    /**
     * This masks an 8-bit version number, the version of the ABI to which this
     * ELF file conforms. This ABI is version 5. A value of 0 denotes unknown conformance.
     * {@value}
     */
    public static final int EF_ARM_ABIMASK  = 0xFF000000;
    public static final int EF_ARM_ABISHIFT  = 24;

    /**
     * ARM ABI version 5.
     * {@value}
     */
    public static final int EF_ARM_ABI5  = 0x05000000;

    /**
     * The ELF file contains BE-8 code, suitable for execution on an ARM
     * Architecture v6 processor. This flag must only be set on an executable file.
     * {@value}
     */
    public static final int EF_ARM_BE8      = 0x00800000;

    /**
     * Legacy code (ABI version 4 and earlier) generated by gcc-arm-xxx might
     * use these bits.
     * {@value}
     */
    public static final int EF_ARM_GCCMASK  = 0x00400FFF;

    /**
     * Set in executable file headers (e_type = ET_EXEC or ET_DYN) to note that
     * the executable file was built to conform to the hardware floating-point
     * procedure-call standard.
     * <p>
     * Compatible with legacy (pre version 5) gcc use as EF_ARM_VFP_FLOAT.
     * </p>
     * <p>
     * Note: This is not used (anymore)
     * </p>
     * {@value}
     */
    public static final int EF_ARM_ABI_FLOAT_HARD  = 0x00000400;

    /**
     * Set in executable file headers (e_type = ET_EXEC or ET_DYN) to note
     * explicitly that the executable file was built to conform to the software
     * floating-point procedure-call standard (the base standard). If both
     * {@link #EF_ARM_ABI_FLOAT_HARD} and {@link #EF_ARM_ABI_FLOAT_SOFT} are clear,
     * conformance to the base procedure-call standard is implied.
     * <p>
     * Compatible with legacy (pre version 5) gcc use as EF_ARM_SOFT_FLOAT.
     * </p>
     * <p>
     * Note: This is not used (anymore)
     * </p>
     * {@value}
     */
    public static final int EF_ARM_ABI_FLOAT_SOFT  = 0x00000200;

    /** Public access to the elf header part-1 (CPU/ABI independent read) */
    public final ElfHeaderPart1 eh1;

    /** Public access to the raw elf header part-2 (CPU/ABI dependent read) */
    public final Ehdr_p2 raw;

    /** Lower case CPUType name */
    public final String cpuName;
    public final CPUType cpuType;
    public final ABIType abiType;

    /** Public access to the {@link SectionHeader} */
    public final SectionHeader[] sht;

    /**
     * Note: The input stream shall stay untouch to be able to read sections!
     *
     * @param in input stream of a binary file at position zero
     * @return
     * @throws IOException if reading from the given input stream fails or less then ELF Header size bytes
     * @throws IllegalArgumentException if the given input stream does not represent an ELF Header
     */
    public static ElfHeaderPart2 read(final ElfHeaderPart1 eh1, final RandomAccessFile in) throws IOException, IllegalArgumentException {
        return new ElfHeaderPart2(eh1, in);
    }

    /**
     * @param buf ELF Header bytes
     * @throws IllegalArgumentException if the given buffer does not represent an ELF Header
     * @throws IOException
     */
    ElfHeaderPart2(final ElfHeaderPart1 eh1, final RandomAccessFile in) throws IllegalArgumentException, IOException {
        this.eh1 = eh1;
        //
        // Part-2
        //
        {
            final byte[] buf = new byte[Ehdr_p2.size(eh1.machDesc.ordinal())];
            readBytes (in, buf, 0, buf.length);
            final ByteBuffer eh2Bytes = ByteBuffer.wrap(buf, 0, buf.length);
            raw = Ehdr_p2.create(eh1.machDesc.ordinal(), eh2Bytes);
        }
        sht = readSectionHeaderTable(in);

        if( CPUFamily.ARM == eh1.cpuType.family && eh1.cpuType.is32Bit ) {
            // AArch64, has no SHT_ARM_ATTRIBUTES or SHT_AARCH64_ATTRIBUTES SectionHeader defined in our builds!
            String armCpuName = null;
            String armCpuRawName = null;
            boolean abiVFPArgsAcceptsVFPVariant = false;
            final SectionHeader sh = getSectionHeader(SectionHeader.SHT_ARM_ATTRIBUTES);
            if(ElfHeaderPart1.DEBUG) {
                System.err.println("ELF-2: Got ARM Attribs Section Header: "+sh);
            }
            if( null != sh ) {
                final SectionArmAttributes sArmAttrs = (SectionArmAttributes) sh.readSection(in);
                if(ElfHeaderPart1.DEBUG) {
                    System.err.println("ELF-2: Got ARM Attribs Section Block : "+sArmAttrs);
                }
                final SectionArmAttributes.Attribute cpuNameArgsAttr = sArmAttrs.get(SectionArmAttributes.Tag.CPU_name);
                if( null != cpuNameArgsAttr && cpuNameArgsAttr.isNTBS() ) {
                    armCpuName = cpuNameArgsAttr.getNTBS();
                }
                final SectionArmAttributes.Attribute cpuRawNameArgsAttr = sArmAttrs.get(SectionArmAttributes.Tag.CPU_raw_name);
                if( null != cpuRawNameArgsAttr && cpuRawNameArgsAttr.isNTBS() ) {
                    armCpuRawName = cpuRawNameArgsAttr.getNTBS();
                }
                final SectionArmAttributes.Attribute abiVFPArgsAttr = sArmAttrs.get(SectionArmAttributes.Tag.ABI_VFP_args);
                if( null != abiVFPArgsAttr ) {
                    abiVFPArgsAcceptsVFPVariant = SectionArmAttributes.abiVFPArgsAcceptsVFPVariant(abiVFPArgsAttr.getULEB128());
                }
            }
            {
                String _cpuName;
                if( null != armCpuName && armCpuName.length() > 0 ) {
                    _cpuName = armCpuName.toLowerCase().replace(' ', '-');
                } else if( null != armCpuRawName && armCpuRawName.length() > 0 ) {
                    _cpuName = armCpuRawName.toLowerCase().replace(' ', '-');
                } else {
                    _cpuName = eh1.cpuName;
                }

                // 1st-try: native name
                CPUType _cpuType = queryCPUTypeSafe(_cpuName);
                if( null == _cpuType ) {
                    // 2nd-try: "arm-" + native name
                    _cpuName = "arm-"+_cpuName;
                    _cpuType = queryCPUTypeSafe(_cpuName);
                    if( null == _cpuType ) {
                        // finally: Use ELF-1
                        _cpuName = eh1.cpuName;
                        _cpuType = queryCPUTypeSafe(_cpuName);
                        if( null == _cpuType ) {
                            throw new InternalError("XXX: "+_cpuName+", "+eh1); // shall not happen
                        }
                    }
                }
                cpuName = _cpuName;
                cpuType = _cpuType;
                if(ElfHeaderPart1.DEBUG) {
                    System.err.println("ELF-2: abiARM cpuName "+_cpuName+"[armCpuName "+armCpuName+", armCpuRawName "+armCpuRawName+"] -> "+cpuName+" -> "+cpuType+", abiVFPArgsAcceptsVFPVariant "+abiVFPArgsAcceptsVFPVariant);
                }
            }
            if( cpuType.is32Bit ) { // always true, see above!
                abiType = abiVFPArgsAcceptsVFPVariant ? ABIType.EABI_GNU_ARMHF : ABIType.EABI_GNU_ARMEL;
            } else {
                abiType = eh1.abiType;
            }
        } else {
            cpuName = eh1.cpuName;
            cpuType = eh1.cpuType;
            abiType = eh1.abiType;
        }
        if(ElfHeaderPart1.DEBUG) {
            System.err.println("ELF-2: cpuName "+cpuName+" -> "+cpuType+", "+abiType);
        }
    }
    private static CPUType queryCPUTypeSafe(final String cpuName) {
        CPUType res = null;
        try {
            res = CPUType.query(cpuName);
        } catch (final Throwable t) {
            if(ElfHeaderPart1.DEBUG) {
                System.err.println("ELF-2: queryCPUTypeSafe("+cpuName+"): "+t.getMessage());
            }
        }
        return res;
    }

    public final short getSize() { return raw.getE_ehsize(); }

    /** Returns the processor-specific flags associated with the file. */
    public final int getFlags() {
        return raw.getE_flags();
    }

    /** Returns the ARM EABI version from {@link #getFlags() flags}, maybe 0 if not an ARM EABI. */
    public byte getArmABI() {
        return (byte) ( ( ( EF_ARM_ABIMASK & raw.getE_flags() ) >> EF_ARM_ABISHIFT ) & 0xff );
    }

    /** Returns the ARM EABI legacy GCC {@link #getFlags() flags}, maybe 0 if not an ARM EABI or not having legacy GCC flags. */
    public int getArmLegacyGCCFlags() {
        final int f = raw.getE_flags();
        return 0 != ( EF_ARM_ABIMASK & f ) ? ( EF_ARM_GCCMASK & f ) : 0;
    }

    /**
     * Returns the ARM EABI float mode from {@link #getFlags() flags},
     * i.e. 1 for {@link #EF_ARM_ABI_FLOAT_SOFT}, 2 for {@link #EF_ARM_ABI_FLOAT_HARD}
     * or 0 for none.
     * <p>
     * Note: This is not used (anymore)
     * </p>
     */
    public byte getArmFloatMode() {
        final int f = raw.getE_flags();
        if( 0 != ( EF_ARM_ABIMASK & f ) ) {
            if( ( EF_ARM_ABI_FLOAT_HARD & f ) != 0 ) {
                return 2;
            }
            if( ( EF_ARM_ABI_FLOAT_SOFT & f ) != 0 ) {
                return 1;
            }
        }
        return 0;
    }

    /** Returns the 1st occurence of matching SectionHeader {@link SectionHeader#getType() type}, or null if not exists. */
    public final SectionHeader getSectionHeader(final int type) {
        for(int i=0; i<sht.length; i++) {
            final SectionHeader sh = sht[i];
            if( sh.getType() == type ) {
                return sh;
            }
        }
        return null;
    }

    /** Returns the 1st occurence of matching SectionHeader {@link SectionHeader#getName() name}, or null if not exists. */
    public final SectionHeader getSectionHeader(final String name) {
        for(int i=0; i<sht.length; i++) {
            final SectionHeader sh = sht[i];
            if( sh.getName().equals(name) ) {
                return sh;
            }
        }
        return null;
    }

    @Override
    public final String toString() {
        final int armABI = getArmABI();
        final String armFlagsS;
        if( 0 != armABI ) {
            armFlagsS=", arm[abi "+armABI+", lGCC "+getArmLegacyGCCFlags()+", float "+getArmFloatMode()+"]";
        } else {
            armFlagsS="";
        }
        return "ELF-2["+cpuType+", "+abiType+", flags["+toHexString(getFlags())+armFlagsS+"], sh-num "+sht.length+"]";
    }

    final SectionHeader[] readSectionHeaderTable(final RandomAccessFile in) throws IOException, IllegalArgumentException {
        // positioning
        {
            final long off = raw.getE_shoff(); // absolute offset
            if( 0 == off ) {
                return new SectionHeader[0];
            }
            seek(in, off);
        }
        final SectionHeader[] sht;
        final int strndx = raw.getE_shstrndx();
        final int size = raw.getE_shentsize();
        final int num;
        int i;
        if( 0 == raw.getE_shnum() ) {
            // Read 1st table 1st and use it's sh_size
            final byte[] buf0 = new byte[size];
            readBytes(in, buf0, 0, size);
            final SectionHeader sh0 = new SectionHeader(this, buf0, 0, size, 0);
            num = (int) sh0.raw.getSh_size();
            if( 0 >= num ) {
                throw new IllegalArgumentException("EHdr sh_num == 0 and 1st SHdr size == 0");
            }
            sht = new SectionHeader[num];
            sht[0] = sh0;
            i=1;
        } else {
            num = raw.getE_shnum();
            sht = new SectionHeader[num];
            i=0;
        }
        for(; i<num; i++) {
            final byte[] buf = new byte[size];
            readBytes(in, buf, 0, size);
            sht[i] = new SectionHeader(this, buf, 0, size, i);
        }
        if( SectionHeader.SHN_UNDEF != strndx ) {
            // has section name string table
            if( shortToInt(SectionHeader.SHN_LORESERVE) <= strndx ) {
                throw new InternalError("TODO strndx: "+SectionHeader.SHN_LORESERVE+" < "+strndx);
            }
            final SectionHeader strShdr = sht[strndx];
            if( SectionHeader.SHT_STRTAB != strShdr.getType() ) {
                throw new IllegalArgumentException("Ref. string Shdr["+strndx+"] is of type "+strShdr.raw.getSh_type());
            }
            final Section strS = strShdr.readSection(in);
            for(i=0; i<num; i++) {
                sht[i].initName(strS, sht[i].raw.getSh_name());
            }
        }

        return sht;
    }
}