#
# Copyright (C) 2016 The Android Open Source Project
#
# 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.
#
"""Parses the contents of a GCDA file generated by the GCC compiler.

The parse() function updates a summary object, which was created by
the GCNO parser, and includes coverage information along arcs and at
code blocks.


    Typical usage example:

    parse(file_name, file_summary)
"""

import struct
import sys

from vts.utils.python.coverage import parser


class GCDAParser(parser.GcovStreamParserUtil):
    """Parser object class stores stateful information for parsing GCDA files.

    Stores the file stream and a FileSummary object as it is updated.

    Attributes:
        checksum: The checksum (int) of the file
        file_summary: The FileSummary object describing the source file
        format: Character denoting the endianness of the file
        stream: File stream object for a GCDA file
    """

    MAGIC = 0x67636461
    TAG_FUNCTION = 0x01000000
    TAG_COUNTER = 0x01a10000
    TAG_OBJECT = 0xa1000000
    TAG_PROGRAM = 0xa3000000

    def __init__(self, stream):
        """Inits the parser with the input stream and default values.

        The byte order is set by default to little endian and the summary file
        must be provided from the output of the GCNOparser.

        Args:
            stream: An input binary file stream to a .gcno file
        """
        self._file_summary = None
        super(GCDAParser, self).__init__(stream, self.MAGIC)

    @property
    def file_summary(self):
        """Gets the FileSummary object where coverage data is stored.

        Returns:
            A FileSummary object.
        """
        return self._file_summary

    @file_summary.setter
    def file_summary(self, file_summary):
        """Sets the FileSummary object in which to store coverage data.

        Args:
            file_summary: A FileSummary object from a processed gcno file
        """
        self._file_summary = file_summary

    def Parse(self, file_summary):
        """Runs the parser on the file opened in the stream attribute.

        Reads coverage information from the GCDA file stream and resolves
        block and edge weights.

        Returns:
            FileSummary object representing the coverage for functions, blocks,
            arcs, and lines in the opened GCNO file.

        Raises:
            parser.FileFormatError: invalid file format or invalid counts.
        """
        self.file_summary = file_summary
        func = None

        while True:
            tag = str()

            try:
                while True:
                    tag = self.ReadInt()
                    if (tag == self.TAG_FUNCTION or tag == self.TAG_COUNTER or
                            tag == self.TAG_OBJECT or tag == self.TAG_PROGRAM):
                        break
                length = self.ReadInt()
            except parser.FileFormatError:
                return self.file_summary  #  end of file reached

            if tag == self.TAG_FUNCTION:
                func = self.ReadFunction(length)
            elif tag == self.TAG_COUNTER:
                self.ReadCounts(func)
                if not func.Resolve():
                    raise parser.FileFormatError(
                        "Corrupt file: Counts could not be resolved.")
            elif tag == self.TAG_OBJECT:
                pass
            elif tag == self.TAG_PROGRAM:
                self.ReadInt()  #  checksum
                for i in range(length - 1):
                    self.ReadInt()

    def ReadFunction(self, length):
        """Reads a function header from the stream.

        Reads information about a function from the gcda file stream and
        returns the function.

        Args:
            func: the function for which coverage information will be read.

        Raises:
            parser.FileFormatError: Corrupt file.
        """
        ident = self.ReadInt()
        func = self.file_summary.functions[ident]
        checksum = self.ReadInt()
        words_read = 3
        if int(self.version[1]) > 4:
            self.ReadInt()
            words_read = 4

        if words_read < length:
            gcda_name = self.ReadString()

        return func

    def ReadCounts(self, func):
        """Reads arc counts from the stream.

        Reads counts from the gcda file stream for arcs that are not
        fake and are not in the tree. Updates their counts and marks them
        as having resolved counts.

        Args:
            func: FunctionSummary for which arc counts will be read.
        """
        for block in func.blocks:
            for arc in block.exit_arcs:
                if not arc.fake and not arc.on_tree:
                    count = self.ReadInt64()
                    arc.count = count
                    arc.resolved = True


def ParseGcdaFile(file_name, file_summary):
    """Parses the .gcno file specified by the input.

    Reads the .gcno file specified and parses the information describing
    basic blocks, functions, and arcs.

    Args:
        file_name: A string file path to a .gcno file
        file_summary: The summary from a parsed gcno file

    Returns:
        A summary object containing information about the coverage for each
        block in each function.
    """

    with open(file_name, 'rb') as stream:
        return GCDAParser(stream).Parse(file_summary)