#
# Copyright 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.
#

import argparse
import os
import os.path
import sys
import re
import fileinput

AR = 'ar'
CC = 'gcc'

class MakeParser(object):
    '''Parses the output of make --dry-run.

    Attributes:
        ltp_root: string, LTP root directory
        ar_parser: archive (ar) command argument parser
        cc_parser: gcc command argument parser
        result: list of string, result string buffer
        dir_stack: list of string, directory stack for parsing make commands
    '''

    def __init__(self, ltp_root):
        self.ltp_root = ltp_root
        ar_parser = argparse.ArgumentParser()
        ar_parser.add_argument('-r', dest='r', action='store_true')
        ar_parser.add_argument('-c', dest='c', action='store')
        self.ar_parser = ar_parser

        cc_parser = argparse.ArgumentParser()
        cc_parser.add_argument('-D', dest='defines', action='append')
        cc_parser.add_argument('-I', dest='includes', action='append')
        cc_parser.add_argument('-l', dest='libraries', action='append')
        cc_parser.add_argument('-c', dest='compile', action='store_true')
        cc_parser.add_argument('-o', dest='target', action='store')
        self.cc_parser = cc_parser

        self.result = []
        self.dir_stack = []

    def GetRelativePath(self, path):
        '''Get relative path toward LTP directory.

        Args:
            path: string, a path to convert to relative path
        '''
        if path[0] == '/':
            path = os.path.realpath(path)
        else:
            path = os.path.realpath(self.ltp_root + os.sep + self.dir_stack[-1]
                                    + os.sep + path)
        return os.path.realpath(path).replace(self.ltp_root + os.sep, '')

    def GetRelativePathForExtensions(self, paths, extensions):
        '''Get relative path toward LTP directory of paths with given extension.

        Args:
            paths: list of string, paths to convert to relative path
            extensions: list of string, extension include filter
        '''
        return [self.GetRelativePath(i) for i in paths if i[-1] in extensions]

    def ParseAr(self, line):
        '''Parse a archive command line.

        Args:
            line: string, a line of ar command to parse
        '''
        args, unparsed = self.ar_parser.parse_known_args(line.split()[1:])

        sources = self.GetRelativePathForExtensions(unparsed, ['o'])
        target = self.GetRelativePath(args.c.replace('"', ""))

        assert len(sources) > 0

        self.result.append("ar['%s'] = %s" % (target, sources))

    def ParseCc(self, line):
        '''Parse a gcc command line.

        Args:
            line: string, a line of gcc command to parse
        '''
        args, unparsed = self.cc_parser.parse_known_args(line.split()[1:])

        sources = self.GetRelativePathForExtensions(unparsed, ['c', 'o'])
        includes = [self.GetRelativePath(i)
                    for i in args.includes] if args.includes else []
        flags = []
        defines = args.defines if args.defines else []
        target = self.GetRelativePath(args.target)

        if args.defines:
            for define in args.defines:
                flags.append('-D%s' % define)

        flags.extend(i for i in unparsed if i.startswith('-Wno'))

        assert len(sources) > 0

        if args.compile:
            self.result.append("cc_compile['%s'] = %s" % (target, sources))
        else:
            libraries = args.libraries if args.libraries else []
            if sources[0].endswith('.o'):
                self.result.append("cc_link['%s'] = %s" % (target, sources))
            else:
                self.result.append("cc_compilelink['%s'] = %s" %
                                   (target, sources))
            self.result.append("cc_libraries['%s'] = %s" % (target, libraries))

        self.result.append("cc_flags['%s'] = %s" % (target, flags))
        self.result.append("cc_includes['%s'] = %s" % (target, includes))

    def ParseFile(self, input_path):
        '''Parses the output of make --dry-run.

        Args:
            input_text: string, output of make --dry-run

        Returns:
            string, generated directives in the form
                    ar['target.a'] = [ 'srcfile1.o, 'srcfile2.o', ... ]
                    cc_link['target'] = [ 'srcfile1.o', 'srcfile2.o', ... ]
                    cc_compile['target.o'] = [ 'srcfile1.c' ]
                    cc_compilelink['target'] = [ 'srcfile1.c' ]
                along with optional flags for the above directives in the form
                    cc_flags['target'] = [ '-flag1', '-flag2', ...]
                    cc_includes['target'] = [ 'includepath1', 'includepath2', ...]
                    cc_libraries['target'] = [ 'library1', 'library2', ...]
        '''
        self.result = []
        self.dir_stack = []

        entering_directory = re.compile(r"make.*: Entering directory `(.*)'")
        leaving_directory = re.compile(r"make.*: Leaving directory `(.*)'")

        with open(input_path, 'r') as f:
            for line in f:
                line = line.strip()

                m = entering_directory.match(line)
                if m:
                    self.dir_stack.append(self.GetRelativePath(m.group(1)))
                    continue

                m = leaving_directory.match(line)
                if m:
                    self.dir_stack.pop()
                elif line.startswith(AR):
                    self.ParseAr(line)
                elif line.startswith(CC):
                    self.ParseCc(line)

        return self.result