/*
 * Copyright 2014 Google Inc. All rights reserved.
 *
 * 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.
 */
using System;
using System.Reflection;using System.Collections.Generic;
using System.IO;

namespace Google.FlatBuffers
{

  /// <summary>
  /// The Class of the Verifier Options
  /// </summary>
  public class Options
  {
    public const int DEFAULT_MAX_DEPTH = 64;
    public const int DEFAULT_MAX_TABLES = 1000000;

    private int max_depth = 0;
    private int max_tables = 0;
    private bool string_end_check = false;
    private bool alignment_check = false;

    public Options()
    {
      max_depth = DEFAULT_MAX_DEPTH;
      max_tables = DEFAULT_MAX_TABLES;
      string_end_check = true;
      alignment_check = true;
    }

    public Options(int maxDepth, int maxTables, bool stringEndCheck, bool alignmentCheck)
    {
      max_depth = maxDepth;
      max_tables = maxTables;
      string_end_check = stringEndCheck;
      alignment_check = alignmentCheck;
    }
    /// <summary> Maximum depth of nested tables allowed in a valid flatbuffer. </summary>
    public int maxDepth
    {
      get { return max_depth; }
      set { max_depth = value; }
    }
    /// <summary> Maximum number of tables allowed in a valid flatbuffer. </summary>
    public int maxTables
    {
      get { return max_tables; }
      set { max_tables = value; }
    }
    /// <summary> Check that string contains its null terminator </summary>
    public bool stringEndCheck
    {
      get { return string_end_check; }
      set { string_end_check = value; }
    }
    /// <summary> Check alignment of elements </summary>
    public bool alignmentCheck
    {
      get { return alignment_check; }
      set { alignment_check = value; }
    }
  }

  public struct checkElementStruct
  {
    public bool elementValid;
    public uint elementOffset;
  }

  public delegate bool VerifyTableAction(Verifier verifier, uint tablePos);
  public delegate bool VerifyUnionAction(Verifier verifier, byte typeId, uint tablePos);

  /// <summary>
  /// The Main Class of the FlatBuffer Verifier
  /// </summary>
  public class Verifier
  {
    private ByteBuffer verifier_buffer = null;
    private Options verifier_options = null;
    private int depth_cnt = 0;
    private int num_tables_cnt = 0;

    public const int SIZE_BYTE = 1;
    public const int SIZE_INT = 4;
    public const int SIZE_U_OFFSET = 4;
    public const int SIZE_S_OFFSET = 4;
    public const int SIZE_V_OFFSET = 2;
    public const int SIZE_PREFIX_LENGTH = FlatBufferConstants.SizePrefixLength;         // default size = 4
    public const int FLATBUFFERS_MAX_BUFFER_SIZE = System.Int32.MaxValue;               // default size = 2147483647
    public const int FILE_IDENTIFIER_LENGTH = FlatBufferConstants.FileIdentifierLength; // default size = 4

    /// <summary> The Base Constructor of the Verifier object </summary>
    public Verifier()
    {
      // Verifier buffer
      verifier_buffer = null;
      // Verifier settings 
      verifier_options = null;
      // Depth counter
      depth_cnt = 0;
      // Tables counter
      num_tables_cnt = 0;
    }

    /// <summary> The Constructor of the Verifier object with input parameters: ByteBuffer and/or Options </summary>
    /// <param name="buf"> Input flat byte buffer defined as ByteBuffer type</param>
    /// <param name="options"> Options object with settings for the coniguration the Verifier </param>
    public Verifier(ByteBuffer buf, Options options = null)
    {
      verifier_buffer = buf;
      verifier_options = options ?? new Options();
      depth_cnt = 0;
      num_tables_cnt = 0;
    }

    /// <summary> Bytes Buffer for Verify</summary>
    public ByteBuffer Buf
    {
      get { return verifier_buffer; }
      set { verifier_buffer = value; }
    }
    /// <summary> Options of the Verifier </summary>
    public Options options
    {
      get { return verifier_options; }
      set { verifier_options = value; }
    }
    /// <summary> Counter of tables depth in a tested flatbuffer  </summary>
    public int depth
    {
      get { return depth_cnt; }
      set { depth_cnt = value; }
    }
    /// <summary> Counter of tables in a tested flatbuffer </summary>
    public int numTables
    {
      get { return num_tables_cnt; }
      set { num_tables_cnt = value; }
    }


    /// <summary> Method set maximum tables depth of valid structure</summary>
    /// <param name="value"> Specify Value of the maximum depth of the structure</param>
    public Verifier SetMaxDepth(int value)
    {
      verifier_options.maxDepth = value;
      return this;
    }
    /// <summary> Specify maximum number of tables in structure </summary>
    /// <param name="value"> Specify Value of the maximum number of the tables in the structure</param>
    public Verifier SetMaxTables(int value)
    {
      verifier_options.maxTables = value;
      return this;
    }
    /// <summary> Enable/disable buffer content alignment check </summary>
    /// <param name="value"> Value of the State for buffer content alignment check (Enable = true) </param>
    public Verifier SetAlignmentCheck(bool value)
    {
      verifier_options.alignmentCheck = value;
      return this;
    }
    /// <summary> Enable/disable checking of string termination '0' character </summary>
    /// <param name="value"> Value of the option for string termination '0' character check (Enable = true)</param>
    public Verifier SetStringCheck(bool value)
    {
      verifier_options.stringEndCheck = value;
      return this;
    }

    /// <summary> Check if there is identifier in buffer </summary>
    /// <param name="buf"> Input flat byte buffer defined as ByteBuffer type </param>
    /// <param name="startPos">Start position of data in the Byte Buffer</param>
    /// <param name="identifier"> Identifier for the Byte Buffer</param>
    /// <returns> Return True when the Byte Buffer Identifier is present</returns>
    private bool BufferHasIdentifier(ByteBuffer buf, uint startPos, string identifier)
    {
      if (identifier.Length != FILE_IDENTIFIER_LENGTH)
      {
        throw new ArgumentException("FlatBuffers: file identifier must be length" + Convert.ToString(FILE_IDENTIFIER_LENGTH));
      }
      for (int i = 0; i < FILE_IDENTIFIER_LENGTH; i++)
      {
        if ((sbyte)identifier[i] != verifier_buffer.GetSbyte(Convert.ToInt32(SIZE_S_OFFSET + i + startPos)))
        {
          return false;
        }
      }

      return true;
    }

    /// <summary> Get UOffsetT from buffer at given position - it must be verified before read </summary>
    /// <param name="buf"> Input flat byte buffer defined as ByteBuffer type </param>
    /// <param name="pos"> Position of data in the Byte Buffer</param>
    /// <returns> Return the UOffset Value (Unsigned Integer type - 4 bytes) in pos </returns>
    private uint ReadUOffsetT(ByteBuffer buf, uint pos)
    {
      return buf.GetUint(Convert.ToInt32(pos));
    }
    /// <summary> Get SOffsetT from buffer at given position - it must be verified before read </summary>
    /// <param name="buf"> Input flat byte buffer defined as ByteBuffer type </param>
    /// <param name="pos"> Position of data in the Byte Buffer</param>
    /// <returns> Return the SOffset Value (Signed Integer type - 4 bytes) in pos </returns>
    private int ReadSOffsetT(ByteBuffer buf, int pos)
    {
      return buf.GetInt(pos);
    }
    /// <summary> Get VOffsetT from buffer at given position - it must be verified before read </summary>
    /// <param name="buf"> Input flat byte buffer defined as ByteBuffer type </param>
    /// <param name="pos"> Position of data in the Byte Buffer</param>
    /// <returns> Return the VOffset Value (Short type - 2 bytes) in pos </returns>
    private short ReadVOffsetT(ByteBuffer buf, int pos)
    {
      return buf.GetShort(pos);
    }

    /// <summary> Get table data area relative offset from vtable. Result is relative to table start
    ///           Fields which are deprecated are ignored by checking against the vtable's length. </summary>
    /// <param name="pos"> Position of data in the Byte Buffer </param>
    /// <param name="vtableOffset"> offset of value in the Table</param>
    /// <returns> Return the relative VOffset Value (Short type - 2 bytes) in calculated offset </returns>
    private short GetVRelOffset(int pos, short vtableOffset)
    {
      short VOffset = 0;
      // Used try/catch because pos typa as int 32bit
      try
      {
        // First, get vtable offset
        short vtable = Convert.ToInt16(pos - ReadSOffsetT(verifier_buffer, pos));
        // Check that offset points to vtable area (is smaller than vtable size)
        if (vtableOffset < ReadVOffsetT(verifier_buffer, vtable))
        {
          // Now, we can read offset value - TODO check this value against size of table data
          VOffset = ReadVOffsetT(verifier_buffer, vtable + vtableOffset);
        }
        else
        {
          VOffset = 0;
        }
      }
      catch (Exception e)
      {
        Console.WriteLine("Exception: {0}", e);
        return VOffset;
      }
      return VOffset;

    }
    /// <summary> Get table data area absolute offset from vtable. Result is the absolute buffer offset.
    /// The result value offset cannot be '0' (pointing to itself) so after validation this method returnes '0'
    /// value as a marker for missing optional entry </summary>
    /// <param name="tablePos"> Table Position value in the Byte Buffer </param>
    /// <param name="vtableOffset"> offset value in the Table</param>
    /// <returns> Return the absolute UOffset Value </returns>
    private uint GetVOffset(uint tablePos, short vtableOffset)
    {
      uint UOffset = 0;
      // First, get vtable relative offset
      short relPos = GetVRelOffset(Convert.ToInt32(tablePos), vtableOffset);
      if (relPos != 0)
      {
        // Calculate offset based on table postion
        UOffset = Convert.ToUInt32(tablePos + relPos);
      }
      else
      {
        UOffset = 0;
      }
      return UOffset;
    }

    /// <summary> Check flatbuffer complexity (tables depth, elements counter and so on) </summary>
    /// <returns> If complexity is too high function returns false as verification error </returns>
    private bool CheckComplexity()
    {
      return ((depth <= options.maxDepth) && (numTables <= options.maxTables));
    }

    /// <summary> Check alignment of element. </summary>
    /// <returns> Return True when alignment of the element is correct</returns>
    private bool CheckAlignment(uint element, ulong align)
    {
      return (((element & (align - 1)) == 0) || (!options.alignmentCheck));
    }

    /// <summary> Check if element is valid in buffer area. </summary> 
    /// <param name="pos"> Value defines the offset/position to element</param>
    /// <param name="elementSize"> Size of element</param>
    /// <returns> Return True when Element is correct </returns>
    private bool CheckElement(uint pos, ulong elementSize)
    {
      return ((elementSize < Convert.ToUInt64(verifier_buffer.Length)) && (pos <= (Convert.ToUInt32(verifier_buffer.Length) - elementSize)));
    }
    /// <summary> Check if element is a valid scalar. </summary>
    /// <param name="pos"> Value defines the offset to scalar</param>
    /// <param name="elementSize"> Size of element</param>
    /// <returns> Return True when Scalar Element is correct </returns>
    private bool CheckScalar(uint pos, ulong elementSize)
    {
      return ((CheckAlignment(pos, elementSize)) && (CheckElement(pos, elementSize)));
    }
    /// <summary> Check offset. It is a scalar with size of UOffsetT. </summary>
    private bool CheckOffset(uint offset)
    {
      return (CheckScalar(offset, SIZE_U_OFFSET));
    }

    private checkElementStruct CheckVectorOrString(uint pos, ulong elementSize)
    {
      var result = new checkElementStruct
      {
        elementValid = false,
        elementOffset = 0
      };

      uint vectorPos = pos;
      // Check we can read the vector/string size field (it is of uoffset size)
      if (!CheckScalar(vectorPos, SIZE_U_OFFSET))
      {
        // result.elementValid = false; result.elementOffset = 0;
        return result;
      }
      // Check the whole array. If this is a string, the byte past the array
      // must be 0.
      uint size = ReadUOffsetT(verifier_buffer, vectorPos);
      ulong max_elements = (FLATBUFFERS_MAX_BUFFER_SIZE / elementSize);
      if (size >= max_elements)
      {
        // Protect against byte_size overflowing. 
        // result.elementValid = false; result.elementOffset = 0;
        return result;
      }

      uint bytes_size = SIZE_U_OFFSET + (Convert.ToUInt32(elementSize) * size);
      uint buffer_end_pos = vectorPos + bytes_size;
      result.elementValid = CheckElement(vectorPos, bytes_size);
      result.elementOffset = buffer_end_pos;
      return (result);
    }

    /// <summary>Verify a string at given position.</summary>
    private bool CheckString(uint pos)
    {
      var result = CheckVectorOrString(pos, SIZE_BYTE);
      if (options.stringEndCheck)
      {
        result.elementValid = result.elementValid && CheckScalar(result.elementOffset, 1); // Must have terminator
        result.elementValid = result.elementValid && (verifier_buffer.GetSbyte(Convert.ToInt32(result.elementOffset)) == 0); // Terminating byte must be 0.
      }
      return result.elementValid;
    }

    /// <summary> Verify the vector of elements of given size </summary>
    private bool CheckVector(uint pos, ulong elementSize)
    {
      var result = CheckVectorOrString(pos, elementSize);
      return result.elementValid;
    }
    /// <summary> Verify table content using structure dependent generated function </summary>
    private bool CheckTable(uint tablePos, VerifyTableAction verifyAction)
    {
      return verifyAction(this, tablePos);
    }

    /// <summary> String check wrapper function to be used in vector of strings check </summary>
    private bool CheckStringFunc(Verifier verifier, uint pos)
    {
      return verifier.CheckString(pos);
    }

    /// <summary> Check vector of objects. Use generated object verification function </summary>
    private bool CheckVectorOfObjects(uint pos, VerifyTableAction verifyAction)
    {
      if (!CheckVector(pos, SIZE_U_OFFSET))
      {
        return false;
      }
      uint size = ReadUOffsetT(verifier_buffer, pos);
      // Vector data starts just after vector size/length
      uint vecStart = pos + SIZE_U_OFFSET;
      uint vecOff = 0;
      // Iterate offsets and verify referenced objects
      for (uint i = 0; i < size; i++)
      {
        vecOff = vecStart + (i * SIZE_U_OFFSET);
        if (!CheckIndirectOffset(vecOff))
        {
          return false;
        }
        uint objOffset = GetIndirectOffset(vecOff);
        if (!verifyAction(this, objOffset))
        {
          return false;
        }
      }
      return true;
    }

    /// <summary> Check if the offset referenced by offsetPos is the valid offset pointing to buffer</summary>
    //  offsetPos - offset to offset data
    private bool CheckIndirectOffset(uint pos)
    {
      // Check the input offset is valid
      if(!CheckScalar(pos, SIZE_U_OFFSET))
      {
        return false;
      }
      // Get indirect offset
      uint offset = ReadUOffsetT(verifier_buffer, pos);
      // May not point to itself neither wrap around  (buffers are max 2GB)
      if ((offset == 0) || (offset >= FLATBUFFERS_MAX_BUFFER_SIZE))
      {
        return false;
      }
      // Must be inside the buffer
      return CheckElement(pos + offset, 1);
    }

    /// <summary> Check flatbuffer content using generated object verification function </summary>
    private bool CheckBufferFromStart(string identifier, uint startPos, VerifyTableAction verifyAction)
    {
      if ((identifier != null) &&
          (identifier.Length == 0) &&
          ((verifier_buffer.Length < (SIZE_U_OFFSET + FILE_IDENTIFIER_LENGTH)) || (!BufferHasIdentifier(verifier_buffer, startPos, identifier))))
      {
        return false;
      }
      if(!CheckIndirectOffset(startPos))
      {
        return false;
      }
      uint offset = GetIndirectOffset(startPos);
      return CheckTable(offset, verifyAction); //  && GetComputedSize()
    }

    /// <summary> Get indirect offset. It is an offset referenced by offset Pos </summary>
    private uint GetIndirectOffset(uint pos)
    {
      // Get indirect offset referenced by offsetPos
      uint offset = pos + ReadUOffsetT(verifier_buffer, pos);
      return offset;
    }

    /// <summary> Verify beginning of table </summary>
    /// <param name="tablePos"> Position in the Table </param>
    /// <returns> Return True when the verification of the beginning of the table is passed</returns> 
    // (this method is used internally by generated verification functions)
    public bool VerifyTableStart(uint tablePos)
    {
      // Starting new table verification increases complexity of structure
      depth_cnt++;
      num_tables_cnt++;

      if (!CheckScalar(tablePos, SIZE_S_OFFSET))
      {
        return false;
      }
      uint vtable = (uint)(tablePos - ReadSOffsetT(verifier_buffer, Convert.ToInt32(tablePos)));
      return ((CheckComplexity()) && (CheckScalar(vtable, SIZE_V_OFFSET)) && (CheckAlignment(Convert.ToUInt32(ReadVOffsetT(verifier_buffer, Convert.ToInt32(vtable))), SIZE_V_OFFSET)) && (CheckElement(vtable, Convert.ToUInt64(ReadVOffsetT(verifier_buffer, Convert.ToInt32(vtable))))));
    }

    /// <summary> Verify end of table. In practice, this function does not check buffer but handles
    /// verification statistics update </summary>
    // (this method is used internally by generated verification functions)
    public bool VerifyTableEnd(uint tablePos)
    {
      depth--;
      return true;
    }

    /// <summary> Verifiy static/inlined data area field </summary>
    /// <param name="tablePos"> Position in the Table</param>
    /// <param name="offsetId"> Offset to the static/inlined data element </param>
    /// <param name="elementSize"> Size of the element </param>
    /// <param name="align"> Alignment bool value </param>
    /// <param name="required"> Required Value when the offset == 0 </param>
    /// <returns>Return True when the verification of the static/inlined data element is passed</returns>
    // (this method is used internally by generated verification functions)
    public bool VerifyField(uint tablePos, short offsetId, ulong elementSize, ulong align, bool required)
    {
      uint offset = GetVOffset(tablePos, offsetId);
      if (offset != 0)
      {
        return ((CheckAlignment(offset, align)) && (CheckElement(offset, elementSize)));
      }
      return !required; // it is OK if field is not required
    }

    /// <summary> Verify string </summary> 
    /// <param name="tablePos"> Position in the Table</param>
    /// <param name="vOffset"> Offset to the String element </param>
    /// <param name="required"> Required Value when the offset == 0 </param>
    /// <returns>Return True when the verification of the String is passed</returns>
    // (this method is used internally by generated verification functions)
    public bool VerifyString(uint tablePos, short vOffset, bool required)
    {
      var offset = GetVOffset(tablePos, vOffset);
      if (offset == 0)
      {
        return !required;
      }
      if (!CheckIndirectOffset(offset))
      {
        return false;
      }
      var strOffset = GetIndirectOffset(offset);
      return CheckString(strOffset);
    }

    /// <summary> Verify vector of fixed size structures and scalars </summary>
    /// <param name="tablePos"> Position in the Table</param>
    /// <param name="vOffset"> Offset to the Vector of Data </param>
    /// <param name="elementSize"> Size of the element</param>
    /// <param name="required"> Required Value when the offset == 0 </param>
    /// <returns>Return True when the verification of the Vector of Data passed</returns>
    // (this method is used internally by generated verification functions)
    public bool VerifyVectorOfData(uint tablePos, short vOffset, ulong elementSize, bool required)
    {
      var offset = GetVOffset(tablePos, vOffset);
      if (offset == 0)
      {
        return !required;
      }
      if (!CheckIndirectOffset(offset))
      {
        return false;
      }
      var vecOffset = GetIndirectOffset(offset);
      return  CheckVector(vecOffset, elementSize);
    }

    /// <summary> Verify array of strings </summary>
    /// <param name="tablePos"> Position in the Table</param>
    /// <param name="offsetId"> Offset to the Vector of String </param>
    /// <param name="required"> Required Value when the offset == 0 </param>
    /// <returns>Return True when the verification of the Vector of String passed</returns>
    // (this method is used internally by generated verification functions)
    public bool VerifyVectorOfStrings(uint tablePos, short offsetId, bool required)
    {
      var offset = GetVOffset(tablePos, offsetId);
      if (offset == 0)
      {
        return !required;
      }
      if (!CheckIndirectOffset(offset))
      {
        return false;
      }
      var vecOffset = GetIndirectOffset(offset);
      return CheckVectorOfObjects(vecOffset, CheckStringFunc); 
    }

    /// <summary> Verify vector of tables (objects). Tables are verified using generated verifyObjFunc </summary>
    /// <param name="tablePos"> Position in the Table</param>
    /// <param name="offsetId"> Offset to the Vector of Table </param>
    /// <param name="verifyAction"> Method used to the verification Table </param>
    /// <param name="required"> Required Value when the offset == 0 </param>
    /// <returns>Return True when the verification of the Vector of Table passed</returns>
    // (this method is used internally by generated verification functions)
    public bool VerifyVectorOfTables(uint tablePos, short offsetId, VerifyTableAction verifyAction, bool required)
    {
      var offset = GetVOffset(tablePos, offsetId);
      if (offset == 0)
      {
        return !required;
      }
      if (!CheckIndirectOffset(offset))
      {
        return false;
      }
      var vecOffset = GetIndirectOffset(offset);
      return CheckVectorOfObjects(vecOffset, verifyAction);
    }

    /// <summary> Verify table object using generated verification function. </summary>
    /// <param name="tablePos"> Position in the Table</param>
    /// <param name="offsetId"> Offset to the Table </param>
    /// <param name="verifyAction"> Method used to the verification Table </param>
    /// <param name="required"> Required Value when the offset == 0 </param>
    /// <returns>Return True when the verification of the Table passed</returns>
    // (this method is used internally by generated verification functions)
    public bool VerifyTable(uint tablePos, short offsetId, VerifyTableAction verifyAction, bool required)
    {
      var offset = GetVOffset(tablePos, offsetId);
      if (offset == 0)
      {
        return !required;
      }
      if (!CheckIndirectOffset(offset))
      {
        return false;
      }
      var tabOffset = GetIndirectOffset(offset);
      return CheckTable(tabOffset, verifyAction);
    }

    /// <summary> Verify nested buffer object. When verifyObjFunc is provided, it is used to verify object structure. </summary>
    /// <param name="tablePos"> Position in the Table </param>
    /// <param name="offsetId"> Offset to the Table </param>
    /// <param name="verifyAction">  Method used to the verification Table </param>
    /// <param name="required"> Required Value when the offset == 0  </param>
    // (this method is used internally by generated verification functions)
    public bool VerifyNestedBuffer(uint tablePos, short offsetId, VerifyTableAction verifyAction, bool required)
    {
      var offset = GetVOffset(tablePos, offsetId);
      if (offset == 0)
      {
        return !required;
      }
      uint vecOffset = GetIndirectOffset(offset);
      if (!CheckVector(vecOffset, SIZE_BYTE))
      {
        return false;
      }
      if (verifyAction != null)
      {
        var vecLength = ReadUOffsetT(verifier_buffer, vecOffset);
        // Buffer begins after vector length
        var vecStart = vecOffset + SIZE_U_OFFSET;
        // Create and Copy nested buffer bytes from part of Verify Buffer
        var nestedByteBuffer = new ByteBuffer(verifier_buffer.ToArray(Convert.ToInt32(vecStart), Convert.ToInt32(vecLength)));
        var nestedVerifyier = new Verifier(nestedByteBuffer, options);
        // There is no internal identifier - use empty one
        if (!nestedVerifyier.CheckBufferFromStart("", 0, verifyAction))
        {
          return false;
        }
      }
      return true;
    }

    /// <summary> Verifiy static/inlined data area at absolute offset </summary>
    /// <param name="pos"> Position of static/inlined data area in the Byte Buffer</param>
    /// <param name="elementSize"> Size of the union data</param>
    /// <param name="align"> Alignment bool value </param>
    /// <returns>Return True when the verification of the Union Data is passed</returns>
    // (this method is used internally by generated verification functions)
    public bool VerifyUnionData(uint pos, ulong elementSize, ulong align)
    {
      bool result = ((CheckAlignment(pos, align)) && (CheckElement(pos, elementSize)));
      return result;
    }

    /// <summary> Verify string referenced by absolute offset value </summary>
    /// <param name="pos"> Position of Union String in the Byte Buffer</param>
    /// <returns>Return True when the verification of the Union String is passed</returns> 
    // (this method is used internally by generated verification functions)
    public bool VerifyUnionString(uint pos)
    {
      bool result = CheckString(pos);
      return result;
    }

    /// <summary> Method verifies union object using generated verification function </summary>
    /// <param name="tablePos"> Position in the Table</param>
    /// <param name="typeIdVOffset"> Offset in the Table</param>
    /// <param name="valueVOffset"> Offset to Element</param>
    /// <param name="verifyAction"> Verification Method used for Union</param>
    /// <param name="required"> Required Value when the offset == 0 </param>
    // (this method is used internally by generated verification functions)
    public bool VerifyUnion(uint tablePos, short typeIdVOffset, short valueVOffset, VerifyUnionAction verifyAction, bool required)
    {
      // Check the union type index
      var offset = GetVOffset(tablePos, typeIdVOffset);
      if (offset == 0)
      {
        return !required;
      }
      if (!((CheckAlignment(offset, SIZE_BYTE)) && (CheckElement(offset, SIZE_BYTE))))
      {
        return false;
      }
      // Take type id
      var typeId = verifier_buffer.Get(Convert.ToInt32(offset));
      // Check union data
      offset = GetVOffset(tablePos, valueVOffset);
      if (offset == 0)
      {
        // When value data is not present, allow union verification function to deal with illegal offset
        return verifyAction(this, typeId, Convert.ToUInt32(verifier_buffer.Length));
      }
      if (!CheckIndirectOffset(offset))
      {
        return false;
      }
      // Take value offset and validate union structure
      uint unionOffset = GetIndirectOffset(offset);
      return verifyAction(this, typeId, unionOffset);
    }

    /// <summary> Verify vector of unions (objects). Unions are verified using generated verifyObjFunc </summary>
    /// <param name="tablePos"> Position of the Table</param>
    /// <param name="typeOffsetId"> Offset in the Table (Union type id)</param>
    /// <param name="offsetId"> Offset to vector of Data Stucture offset</param>
    /// <param name="verifyAction"> Verification Method used for Union</param>
    /// <param name="required"> Required Value when the offset == 0 </param>
    /// <returns>Return True when the verification of the Vector of Unions passed</returns>
    // (this method is used internally by generated verification functions)
    public bool VerifyVectorOfUnion(uint tablePos, short typeOffsetId, short offsetId, VerifyUnionAction verifyAction, bool required)
    {
      // type id offset must be valid
      var offset = GetVOffset(tablePos, typeOffsetId);
      if (offset == 0)
      {
        return !required;
      }
      if (!CheckIndirectOffset(offset))
      {
        return false;
      }
      // Get type id table absolute offset
      var typeIdVectorOffset = GetIndirectOffset(offset);
      // values offset must be valid
      offset = GetVOffset(tablePos, offsetId);
      if (!CheckIndirectOffset(offset))
      {
        return false;
      }
      var valueVectorOffset = GetIndirectOffset(offset);
      // validate referenced vectors
      if(!CheckVector(typeIdVectorOffset, SIZE_BYTE) ||
         !CheckVector(valueVectorOffset, SIZE_U_OFFSET))
      {
        return false;
      }
      // Both vectors should have the same length
      var typeIdVectorLength = ReadUOffsetT(verifier_buffer, typeIdVectorOffset);
      var valueVectorLength = ReadUOffsetT(verifier_buffer, valueVectorOffset);
      if (typeIdVectorLength != valueVectorLength)
      {
        return false;
      }
      // Verify each union from vectors
      var typeIdStart = typeIdVectorOffset + SIZE_U_OFFSET;
      var valueStart = valueVectorOffset + SIZE_U_OFFSET;
      for (uint i = 0; i < typeIdVectorLength; i++)
      {
        // Get type id
        byte typeId = verifier_buffer.Get(Convert.ToInt32(typeIdStart + i * SIZE_U_OFFSET));
        // get offset to vector item
        uint off = valueStart + i * SIZE_U_OFFSET;
        // Check the vector item has a proper offset
        if (!CheckIndirectOffset(off))
        {
          return false;
        }
        uint valueOffset = GetIndirectOffset(off);
        // Verify object
        if (!verifyAction(this, typeId, valueOffset))
        {
          return false;
        }
      }
      return true;
    }

    // Method verifies flatbuffer data using generated Table verification function.
    // The data buffer is already provided when creating [Verifier] object (see [NewVerifier])
    //
    //   - identifier - the expected identifier of buffer data.
    //     When empty identifier is provided the identifier validation is skipped.
    //   - sizePrefixed - this flag should be true when buffer is prefixed with content size
    //   - verifyObjFunc - function to be used for verification. This function is generated by compiler and included in each table definition file with name "<Tablename>Verify"
    //
    // Example:
    //
    //  /* Verify Monster table. Ignore buffer name and assume buffer does not contain data length prefix */
    //  isValid = verifier.verifyBuffer(bb, false, MonsterVerify)
    //
    //  /* Verify Monster table. Buffer name is 'MONS' and contains data length prefix */
    //  isValid = verifier.verifyBuffer("MONS", true, MonsterVerify)
    /// <summary> Method verifies flatbuffer data using generated Table verification function </summary>
    /// 
    /// <param name="identifier"> The expected identifier of buffer data</param>
    /// <param name="sizePrefixed"> Flag should be true when buffer is prefixed with content size</param>
    /// <param name="verifyAction"> Function to be used for verification. This function is generated by compiler and included in each table definition file</param>
    /// <returns> Return True when verification of FlatBuffer passed</returns>
    /// <example>
    /// Example 1. Verify Monster table. Ignore buffer name and assume buffer does not contain data length prefix 
    /// <code>  isValid = verifier.VerifyBuffer(bb, false, MonsterVerify)</code>
    /// Example 2. Verify Monster table. Buffer name is 'MONS' and contains data length prefix 
    /// <code>  isValid = verifier.VerifyBuffer("MONS", true, MonsterVerify)</code>
    /// </example>
    public bool VerifyBuffer(string identifier, bool sizePrefixed, VerifyTableAction verifyAction)
    {
      // Reset counters - starting verification from beginning
      depth = 0;
      numTables = 0;

      var start = (uint)(verifier_buffer.Position);
      if (sizePrefixed) 
      {
        start = (uint)(verifier_buffer.Position) + SIZE_PREFIX_LENGTH;
        if(!CheckScalar((uint)(verifier_buffer.Position), SIZE_PREFIX_LENGTH))
        {
          return false;
        }
        uint size = ReadUOffsetT(verifier_buffer, (uint)(verifier_buffer.Position));
        if (size != ((uint)(verifier_buffer.Length) - start))
        {
          return false;
        }
      } 
      return CheckBufferFromStart(identifier, start, verifyAction);
    }
  }

}
