/*
 * Copyright 2016-present Facebook, Inc.
 *
 * 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.facebook.buck.cxx.elf;

import com.google.common.collect.ImmutableList;

import java.nio.ByteBuffer;

// CHECKSTYLE.OFF: LocalVariableName
// CHECKSTYLE.OFF: ParameterName
public class ElfSymbolTable {

  public final ImmutableList<Entry> entries;

  public ElfSymbolTable(ImmutableList<Entry> entries) {
    this.entries = entries;
  }

  public static ElfSymbolTable parse(ElfHeader.EIClass eiClass, ByteBuffer buffer) {
    ImmutableList.Builder<Entry> entries = ImmutableList.builder();
    while (buffer.hasRemaining()) {
      entries.add(Entry.parse(eiClass, buffer));
    }
    return new ElfSymbolTable(entries.build());
  }

  public void write(ElfHeader.EIClass eiClass, ByteBuffer buffer) {
    for (Entry entry : entries) {
      entry.write(eiClass, buffer);
    }
  }

  /**
   * Encapsulate the data in an ELF section header.
   */
  public static class Entry {

    // CHECKSTYLE.OFF: MemberName
    public final long st_name;
    public final Info st_info;
    public final int st_other;
    public final int st_shndx;
    public final long st_value;
    public final long st_size;
    // CHECKSTYLE.ON: MemberName

    public Entry(
        long st_name,
        Info st_info,
        int st_other,
        int st_shndx,
        long st_value,
        long st_size) {
      this.st_name = st_name;
      this.st_info = st_info;
      this.st_other = st_other;
      this.st_shndx = st_shndx;
      this.st_value = st_value;
      this.st_size = st_size;
    }

    /**
     * @return either a 32- or 64-bit ELF symbol table entry header parsed from the given buffer.
     */
    static Entry parse(ElfHeader.EIClass eiClass, ByteBuffer buffer) {
      if (eiClass == ElfHeader.EIClass.ELFCLASS32) {
        long st_name = Elf.Elf32.getElf32Word(buffer);
        long st_value = Elf.Elf32.getElf32Addr(buffer);
        long st_size = Elf.Elf32.getElf32Word(buffer);
        Info st_info = Info.parse(buffer);
        int st_other = buffer.get();
        int st_shndx = Elf.Elf32.getElf32Half(buffer);
        return new Entry(st_name, st_info, st_other, st_shndx, st_value, st_size);
      } else {
        long st_name = Elf.Elf64.getElf64Word(buffer);
        Info st_info = Info.parse(buffer);
        int st_other = buffer.get();
        int st_shndx = Elf.Elf64.getElf64Half(buffer);
        long st_value = Elf.Elf64.getElf64Addr(buffer);
        long st_size = Elf.Elf64.getElf64Xword(buffer);
        return new Entry(st_name, st_info, st_other, st_shndx, st_value, st_size);
      }
    }

    void write(ElfHeader.EIClass eiClass, ByteBuffer buffer) {
      if (eiClass == ElfHeader.EIClass.ELFCLASS32) {
        Elf.Elf32.putElf32Word(buffer, (int) st_name);
        Elf.Elf32.putElf32Addr(buffer, (int) st_value);
        Elf.Elf32.putElf32Word(buffer, (int) st_size);
        st_info.write(buffer);
        buffer.put((byte) st_other);
        Elf.Elf32.putElf32Half(buffer, (short) st_shndx);
      } else {
        Elf.Elf64.putElf64Word(buffer, (int) st_name);
        st_info.write(buffer);
        buffer.put((byte) st_other);
        Elf.Elf64.putElf64Half(buffer, (short) st_shndx);
        Elf.Elf64.putElf64Addr(buffer, st_value);
        Elf.Elf64.putElf64Xword(buffer, st_size);
      }
    }

    public static class Info {

      // CHECKSTYLE.OFF: MemberName
      public final Bind st_bind;
      public final Type st_type;
      // CHECKSTYLE.ON: MemberName

      public Info(Bind st_bind, Type st_type) {
        this.st_bind = st_bind;
        this.st_type = st_type;
      }

      public static Info parse(ByteBuffer buffer) {
        int st_info = buffer.get();
        return new Info(Bind.valueOf(st_info >> 4), Type.valueOf(st_info & 0xF));
      }

      public void write(ByteBuffer buffer) {
        buffer.put((byte) ((st_bind.value << 4) + (st_type.value & 0xF)));
      }

      public enum Bind {

        STB_LOCAL(0),
        STB_GLOBAL(1),
        STB_WEAK(2),
        ;

        private int value;

        Bind(int value) {
          this.value = value;
        }

        public static Bind valueOf(int val) {
          for (Bind bind : values()) {
            if (bind.value == val) {
              return bind;
            }
          }
          throw new IllegalArgumentException();
        }

      }

      public enum Type {

        STT_NOTYPE(0),
        STT_OBJECT(1),
        STT_FUNC(2),
        STT_SECTION(3),
        STT_FILE(4),
        STT_COMMON(5),
        STT_TLS(6),
        ;

        private int value;

        Type(int value) {
          this.value = value;
        }

        public static Type valueOf(int val) {
          for (Type type : values()) {
            if (type.value == val) {
              return type;
            }
          }
          throw new IllegalArgumentException();
        }

      }

    }

  }

}

@@ -4256,23 +4256,26 @@
 fo(Bind.
-v
+ofIntV
 alue
-Of
 (st_info
@@ -4287,23 +4287,26 @@
 ), Type.
-v
+ofIntV
 alue
-Of
 (st_info
@@ -4678,23 +4678,26 @@
 ic Bind 
-v
+ofIntV
 alue
-Of
 (int val
@@ -5215,15 +5215,18 @@
 ype 
-v
+ofIntV
 alue
-Of
 (int

Fix pmd crash