/*
 * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */



package jdk.tools.jaotc.binformat.pecoff;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import jdk.tools.jaotc.binformat.BinaryContainer;
import jdk.tools.jaotc.binformat.ByteContainer;
import jdk.tools.jaotc.binformat.CodeContainer;
import jdk.tools.jaotc.binformat.ReadOnlyDataContainer;
import jdk.tools.jaotc.binformat.Relocation;
import jdk.tools.jaotc.binformat.Relocation.RelocType;
import jdk.tools.jaotc.binformat.Symbol;
import jdk.tools.jaotc.binformat.Symbol.Binding;
import jdk.tools.jaotc.binformat.Symbol.Kind;
import jdk.tools.jaotc.binformat.pecoff.PECoff.IMAGE_FILE_HEADER;
import jdk.tools.jaotc.binformat.pecoff.PECoff.IMAGE_RELOCATION;
import jdk.tools.jaotc.binformat.pecoff.PECoff.IMAGE_SECTION_HEADER;
import jdk.tools.jaotc.binformat.pecoff.PECoff.IMAGE_SYMBOL;

public class JPECoffRelocObject {

    private final BinaryContainer binContainer;

    private final PECoffContainer pecoffContainer;

    private final int sectionAlignment;

    public JPECoffRelocObject(BinaryContainer binContainer, String outputFileName) {
        this.binContainer = binContainer;
        this.pecoffContainer = new PECoffContainer(outputFileName);
        this.sectionAlignment = binContainer.getCodeSegmentSize();
    }

    private static PECoffSection createByteSection(ArrayList<PECoffSection> sections, String sectName, byte[] scnData,
                    boolean hasRelocs, int scnFlags, int sectAlign) {

        PECoffSection sect = new PECoffSection(sectName, scnData, scnFlags, sectAlign, hasRelocs, sections.size());
        // Add this section to our list
        sections.add(sect);

        return (sect);
    }

    private static void createByteSection(ArrayList<PECoffSection> sections, ByteContainer c, int scnFlags, int sectAlign) {
        PECoffSection sect;
        boolean hasRelocs = c.hasRelocations();
        byte[] scnData = c.getByteArray();

        sect = createByteSection(sections, c.getContainerName(), scnData, hasRelocs, scnFlags, sectAlign);

        c.setSectionId(sect.getSectionId());
    }

    private void createCodeSection(ArrayList<PECoffSection> sections, CodeContainer c) {
        int scnFlags = IMAGE_SECTION_HEADER.IMAGE_SCN_MEM_READ | IMAGE_SECTION_HEADER.IMAGE_SCN_MEM_EXECUTE | IMAGE_SECTION_HEADER.IMAGE_SCN_CNT_CODE;
        createByteSection(sections, c, scnFlags, sectionAlignment);
    }

    private void createReadOnlySection(ArrayList<PECoffSection> sections, ReadOnlyDataContainer c) {
        int scnFlags = IMAGE_SECTION_HEADER.IMAGE_SCN_MEM_READ | IMAGE_SECTION_HEADER.IMAGE_SCN_CNT_INITIALIZED_DATA;
        createByteSection(sections, c, scnFlags, sectionAlignment);
    }

    private void createReadWriteSection(ArrayList<PECoffSection> sections, ByteContainer c) {
        int scnFlags = IMAGE_SECTION_HEADER.IMAGE_SCN_MEM_READ | IMAGE_SECTION_HEADER.IMAGE_SCN_MEM_WRITE;

        if (c.getByteArray().length > 0) {
            scnFlags |= IMAGE_SECTION_HEADER.IMAGE_SCN_CNT_INITIALIZED_DATA;
        } else {
            scnFlags |= IMAGE_SECTION_HEADER.IMAGE_SCN_CNT_UNINITIALIZED_DATA;
        }
        createByteSection(sections, c, scnFlags, sectionAlignment);
    }

    /**
     * Creates a PECoff relocatable object.
     *
     * @param relocationTable
     * @param symbols
     * @throws IOException throws {@code IOException} as a result of file system access failures.
     */
    public void createPECoffRelocObject(Map<Symbol, List<Relocation>> relocationTable, Collection<Symbol> symbols) throws IOException {
        ArrayList<PECoffSection> sections = new ArrayList<>();

        // Create text section
        createCodeSection(sections, binContainer.getCodeContainer());
        createReadOnlySection(sections, binContainer.getMetaspaceNamesContainer());
        createReadOnlySection(sections, binContainer.getKlassesOffsetsContainer());
        createReadOnlySection(sections, binContainer.getMethodsOffsetsContainer());
        createReadOnlySection(sections, binContainer.getKlassesDependenciesContainer());
        createReadOnlySection(sections, binContainer.getMethodMetadataContainer());
        createReadOnlySection(sections, binContainer.getStubsOffsetsContainer());
        createReadOnlySection(sections, binContainer.getHeaderContainer().getContainer());
        createReadOnlySection(sections, binContainer.getCodeSegmentsContainer());
        createReadOnlySection(sections, binContainer.getConstantDataContainer());
        createReadOnlySection(sections, binContainer.getConfigContainer());
        createReadWriteSection(sections, binContainer.getKlassesGotContainer());
        createReadWriteSection(sections, binContainer.getCountersGotContainer());
        createReadWriteSection(sections, binContainer.getMetadataGotContainer());
        createReadWriteSection(sections, binContainer.getMethodStateContainer());
        createReadWriteSection(sections, binContainer.getOopGotContainer());
        createReadWriteSection(sections, binContainer.getExtLinkageGOTContainer());

        // Allocate PECoff Header
        PECoffHeader header = new PECoffHeader();

        // Get PECoff symbol data from BinaryContainer object's symbol tables
        PECoffSymtab symtab = createPECoffSymbolTables(symbols);

        // Add Linker Directives Section
        int scnFlags = IMAGE_SECTION_HEADER.IMAGE_SCN_LNK_INFO | IMAGE_SECTION_HEADER.IMAGE_SCN_LNK_REMOVE;
        createByteSection(sections, ".drectve", symtab.getDirectiveArray(), false, scnFlags, 1 /*
                                                                                                * 1
                                                                                                * byte
                                                                                                * alignment
                                                                                                */);

        // Create the Relocation Tables
        PECoffRelocTable pecoffRelocs = createPECoffRelocTable(sections, relocationTable);

        // File Output Order
        //
        // HEADER (Need address of Symbol Table + symbol count)
        // SECTIONS (Need pointer to Section Data, Relocation Table)
        // DIRECTIVES
        // SYMBOL TABLE
        // SYMBOLS
        // SECTION DATA
        // RELOCATION TABLE

        // Calculate Offset for Symbol table
        int fileOffset = IMAGE_FILE_HEADER.totalsize +
                        (IMAGE_SECTION_HEADER.totalsize * sections.size());

        // Update Header fields
        header.setSectionCount(sections.size());
        header.setSymbolCount(symtab.getSymtabCount());
        header.setSymbolOff(fileOffset);

        // Calculate file offset for first section
        fileOffset += ((symtab.getSymtabCount() * IMAGE_SYMBOL.totalsize) +
                        symtab.getStrtabSize());
        // And round it up
        fileOffset = (fileOffset + (sections.get(0).getDataAlign() - 1)) &
                        ~((sections.get(0).getDataAlign() - 1));

        // Calc file offsets for section data
        for (int i = 0; i < sections.size(); i++) {
            PECoffSection sect = sections.get(i);
            fileOffset = (fileOffset + (sect.getDataAlign() - 1)) &
                            ~((sect.getDataAlign() - 1));
            sect.setOffset(fileOffset);
            fileOffset += sect.getSize();
        }

        // Update relocation sizing information in each section
        for (int i = 0; i < sections.size(); i++) {
            PECoffSection sect = sections.get(i);
            if (sect.hasRelocations()) {
                int nreloc = pecoffRelocs.getNumRelocs(i);
                sect.setReloff(fileOffset);
                sect.setRelcount(nreloc);
                // extended relocations add an addition entry
                if (nreloc > 0xFFFF) {
                    nreloc++;
                }
                fileOffset += (nreloc * IMAGE_RELOCATION.totalsize);
            }
        }

        // Write out the Header
        pecoffContainer.writeBytes(header.getArray());

        // Write out the section table
        for (int i = 0; i < sections.size(); i++) {
            PECoffSection sect = sections.get(i);
            pecoffContainer.writeBytes(sect.getArray(), PECoffSection.getShdrAlign());
        }

        // Write out the symbol table and string table
        pecoffContainer.writeBytes(symtab.getSymtabArray(), 4);
        pecoffContainer.writeBytes(symtab.getStrtabArray(), 1);

        // Write out each section contents
        for (int i = 0; i < sections.size(); i++) {
            PECoffSection sect = sections.get(i);
            pecoffContainer.writeBytes(sect.getDataArray(), sect.getDataAlign());
        }

        // Write out Relocation Tables
        for (int i = 0; i < sections.size(); i++) {
            if (pecoffRelocs.getNumRelocs(i) > 0) {
                pecoffContainer.writeBytes(pecoffRelocs.getRelocData(i));
            }
        }
        pecoffContainer.close();
    }

    /**
     * Construct PECoff symbol data from BinaryContainer object's symbol tables. Both dynamic PECoff
     * symbol table and PECoff symbol table are created from BinaryContainer's symbol info.
     *
     * @param symbols
     */
    private static PECoffSymtab createPECoffSymbolTables(Collection<Symbol> symbols) {
        PECoffSymtab symtab = new PECoffSymtab();

        // First, create the initial null symbol. This is a local symbol.
        // symtab.addSymbolEntry("", (byte)0, (byte)0, (byte)0, 0, 0);

        // Now create PECoff symbol entries for all symbols.
        for (Symbol symbol : symbols) {
            // Get the index of section this symbol is defined in.
            int secHdrIndex = symbol.getSection().getSectionId();
            PECoffSymbol pecoffSymbol = symtab.addSymbolEntry(symbol.getName(), getPECoffTypeOf(symbol), getPECoffClassOf(symbol), (byte) secHdrIndex, symbol.getOffset());
            symbol.setNativeSymbol(pecoffSymbol);
        }
        return (symtab);
    }

    private static byte getPECoffTypeOf(Symbol sym) {
        Kind kind = sym.getKind();
        if (kind == Symbol.Kind.NATIVE_FUNCTION || kind == Symbol.Kind.JAVA_FUNCTION) {
            return IMAGE_SYMBOL.IMAGE_SYM_DTYPE_FUNCTION;
        }
        return IMAGE_SYMBOL.IMAGE_SYM_DTYPE_NONE;
    }

    private static byte getPECoffClassOf(Symbol sym) {
        Binding binding = sym.getBinding();
        if (binding == Symbol.Binding.GLOBAL) {
            return IMAGE_SYMBOL.IMAGE_SYM_CLASS_EXTERNAL;
        }
        return IMAGE_SYMBOL.IMAGE_SYM_CLASS_STATIC;
    }

    /**
     * Construct a PECoff relocation table from BinaryContainer object's relocation tables.
     *
     * @param sections
     * @param relocationTable
     */
    private PECoffRelocTable createPECoffRelocTable(ArrayList<PECoffSection> sections, Map<Symbol, List<Relocation>> relocationTable) {

        PECoffRelocTable pecoffRelocTable = new PECoffRelocTable(sections.size());
        /*
         * For each of the symbols with associated relocation records, create a PECoff relocation
         * entry.
         */
        for (Map.Entry<Symbol, List<Relocation>> entry : relocationTable.entrySet()) {
            List<Relocation> relocs = entry.getValue();
            Symbol symbol = entry.getKey();

            for (Relocation reloc : relocs) {
                createRelocation(symbol, reloc, pecoffRelocTable);
            }
        }

        for (Map.Entry<Symbol, Relocation> entry : binContainer.getUniqueRelocationTable().entrySet()) {
            createRelocation(entry.getKey(), entry.getValue(), pecoffRelocTable);
        }

        return (pecoffRelocTable);
    }

    private static void createRelocation(Symbol symbol, Relocation reloc, PECoffRelocTable pecoffRelocTable) {
        RelocType relocType = reloc.getType();

        int pecoffRelocType = getPECoffRelocationType(relocType);
        PECoffSymbol sym = (PECoffSymbol) symbol.getNativeSymbol();
        int symno = sym.getIndex();
        int sectindex = reloc.getSection().getSectionId();
        int offset = reloc.getOffset();
        int addend = 0;

        switch (relocType) {
            case JAVA_CALL_DIRECT:
            case STUB_CALL_DIRECT:
            case FOREIGN_CALL_INDIRECT_GOT: {
                // Create relocation entry
                addend = -4; // Size in bytes of the patch location
                // Relocation should be applied at the location after call operand
                offset = offset + reloc.getSize() + addend;
                break;
            }
            case JAVA_CALL_INDIRECT: {
                // Do nothing.
                return;
            }
            case METASPACE_GOT_REFERENCE:
            case EXTERNAL_PLT_TO_GOT: {
                addend = -4; // Size of 32-bit address of the GOT
                /*
                 * Relocation should be applied before the test instruction to the move instruction.
                 * reloc.getOffset() points to the test instruction after the instruction that loads
                 * the address of polling page. So set the offset appropriately.
                 */
                offset = offset + addend;
                break;
            }
            case EXTERNAL_GOT_TO_PLT: {
                // this is load time relocations
                break;
            }
            default:
                throw new InternalError("Unhandled relocation type: " + relocType);
        }
        pecoffRelocTable.createRelocationEntry(sectindex, offset, symno, pecoffRelocType);
    }

    // Return IMAGE_RELOCATION Type based on relocType
    private static int getPECoffRelocationType(RelocType relocType) {
        int pecoffRelocType = 0; // R_<ARCH>_NONE if #define'd to 0 for all values of ARCH
        switch (PECoffTargetInfo.getPECoffArch()) {
            case IMAGE_FILE_HEADER.IMAGE_FILE_MACHINE_AMD64:
                if (relocType == RelocType.JAVA_CALL_DIRECT ||
                                relocType == RelocType.FOREIGN_CALL_INDIRECT_GOT) {
                    pecoffRelocType = IMAGE_RELOCATION.IMAGE_REL_AMD64_REL32;
                } else if (relocType == RelocType.STUB_CALL_DIRECT) {
                    pecoffRelocType = IMAGE_RELOCATION.IMAGE_REL_AMD64_REL32;
                } else if (relocType == RelocType.JAVA_CALL_INDIRECT) {
                    pecoffRelocType = IMAGE_RELOCATION.IMAGE_REL_AMD64_ABSOLUTE;
                } else if (relocType == RelocType.METASPACE_GOT_REFERENCE ||
                                relocType == RelocType.EXTERNAL_PLT_TO_GOT) {
                    pecoffRelocType = IMAGE_RELOCATION.IMAGE_REL_AMD64_REL32;
                } else if (relocType == RelocType.EXTERNAL_GOT_TO_PLT) {
                    pecoffRelocType = IMAGE_RELOCATION.IMAGE_REL_AMD64_ADDR64;
                } else {
                    assert false : "Unhandled relocation type: " + relocType;
                }
                break;
            default:
                System.out.println("Relocation Type mapping: Unhandled architecture");
        }
        return pecoffRelocType;
    }
}
