# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import logging

from analysis.occurrence import RankByOccurrence
from analysis.project import Project
from analysis.type_enums import LanguageType


class ProjectClassifier(object):
  """Determines the project of a crash - (project_name, project_path).

  For example: ('chromium', 'src'), ('skia', 'src/skia'), ...etc.
  """

  def __init__(self, projects, top_n_frames,
               non_chromium_project_rank_priority=None):
    super(ProjectClassifier, self).__init__()
    self.projects = projects
    self.top_n_frames = top_n_frames
    self.non_chromium_project_rank_priority = non_chromium_project_rank_priority

  @staticmethod
  def _GetTopProject(projects, rank_function=None):
    """Gets the highest ranking class among projects."""
    projects = RankByOccurrence(projects, 1, rank_function=rank_function)

    if projects:
      return projects[0]

    logging.warning('ProjectClassifier.Classify: no projects found.')
    return None

  def ClassifyCallStack(self, crash_stack):
    """Determines which project is responsible for this crash stack.

    Args:
      crash_stack (CallStack): The crash_stack of the Stacktrace for this crash.

    Returns:
      The name of the most-suspected project; or the empty string on failure.
    """
    rank_function = None
    if crash_stack.language_type == LanguageType.JAVA:
      def RankFunctionForJava(occurrence):
        if 'chromium' in occurrence.name:
          index = 0
        else:
          index = self.non_chromium_project_rank_priority[occurrence.name]
        return -len(occurrence), index

      rank_function = RankFunctionForJava

    def GetProjectFromStackFrame(frame):
      """Determine which project is responsible for this frame."""
      for project in self.projects:
        if project.MatchesStackFrame(frame):
          return project.GetName(frame.dep_path)

      return None

    projects = map(GetProjectFromStackFrame,
                   crash_stack.frames[:self.top_n_frames])

    return ProjectClassifier._GetTopProject(projects,
                                            rank_function=rank_function)

  def ClassifyDepPath(self, dep_path):
    """Determine which project is responsible for this dep_path."""
    for project in self.projects:
      if project.MatchesDepPath(dep_path):
        return project.GetName(dep_path)

    return ''

  def ClassifySuspect(self, suspect):
    """Determine which project is responsible for this suspect."""
    if not suspect or not suspect.changelog:
      return None

    def GetProjectFromTouchedFile(touched_file):
      for project in self.projects:
        if project.MatchesTouchedFile(suspect.dep_path,
                                      touched_file.changed_path):
          return project.GetName(suspect.dep_path)

      return None

    projects = map(GetProjectFromTouchedFile, suspect.changelog.touched_files)
    return ProjectClassifier._GetTopProject(projects,
                                            rank_function=lambda x:-len(x))

  def ClassifySuspects(self, suspects):
    """Determines which project is resposible for these suspects.

    Args:
      suspects (list of Suspect): culprit suspects.

    Returns:
      The name of the most-suspected project; or the empty string on failure.
    """
    projects = map(self.ClassifySuspect, suspects)
    return ProjectClassifier._GetTopProject(projects,
                                            rank_function=lambda x:-len(x))
