#!/usr/bin/python3
#
# Copyright (C) 2023 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.
#
"""Convert the compare result csv file to a spreadsheet.
Prerequisite:
  - Install the `gspread` python package.
  - Create credentials to allow access to spreadsheets via Google Sheets API.

Usage example:
python3 generate_spread_sheet.py \
  --compared_result compare_result/diff.csv \
  --sheet_name "CTS Compare Result" \
  --credentials_dir ~/.config/gspread/
"""

import argparse
import constant
import csv
import gspread
import os

from typing import List, Tuple


_COLOR_GREY = {'red': 0.37, 'green': 0.42, 'blue': 0.42}
_COLOR_WHITE = {'red': 0.95, 'green': 0.95, 'blue': 0.95}
_COLOR_YELLOW = {'red': 0.9, 'green': 0.8, 'blue': 0.07}
_COLOR_DARK_BLUE = {'red': 0.15, 'green': 0.15, 'blue': 0.46}


_SHEET_HEADER_FORMAT = {
    'backgroundColor': _COLOR_GREY,
    'horizontalAlignment': 'CENTER',
    'textFormat': {
        'foregroundColor': _COLOR_WHITE,
        'fontSize': 11,
        'bold': True,
    },
}


_MODULE_HEADER_FORMAT = {
    'backgroundColor': _COLOR_YELLOW,
    'horizontalAlignment': 'LEFT',
    'textFormat': {
        'foregroundColor': _COLOR_DARK_BLUE,
        'fontSize': 10,
        'bold': True,
    },
}


# The first four columns in compare_results are for test info.
_NUM_OF_INFO_COLUMNS = 4


def _parse_args() -> argparse.Namespace:
  """Parse the script arguments.

  Returns:
    An object of argparse.Namespace.
  """
  parser = argparse.ArgumentParser()
  parser.add_argument('--compared_result', required=True,
                      help='Path to the compared csv file.')
  parser.add_argument('--sheet_name', required=True,
                      help='Name for the output spreadsheet.')
  parser.add_argument('--credentials_dir', required=True,
                      help='Path to the directory that contains gspread '
                           'credentials files.')
  return parser.parse_args()


def _read_csv(csv_path: str) -> Tuple[List[str], List[List[str]]]:
  """Read a csv comparison report and return as lists.

  Args:
    csv_path: The path to the csv comparison report.

  Returns:
    A List of report names, A List of parsed results.
  """
  parsed_result = []
  with open(csv_path, 'r') as csvfile:
    result_reader = csv.reader(csvfile, delimiter=',')
    header = next(result_reader)
    report_names = header[_NUM_OF_INFO_COLUMNS:]
    for row in result_reader:
      parsed_result.append(row)
  return report_names, parsed_result


def _create_spread_sheet(
    new_sheet_name: str, credentials_dir: str
) -> gspread.Spreadsheet:
  """Create a spread sheet at the user's Drive directory.

  Args:
    new_sheet_name: The name of this spread sheet.
    credentials_dir: The path to the directory that contains gspread
                     credentials files.

  Returns:
    An object of gspread.Spreadsheet.
  """
  credentials = os.path.join(credentials_dir, 'credentials.json')
  authorized_user = os.path.join(credentials_dir, 'authorized_user.json')
  gc = gspread.oauth(credentials_filename=credentials,
                     authorized_user_filename=authorized_user)
  sh = gc.create(new_sheet_name)
  return sh


def _get_range_cell(
    begin_row: int, begin_column: str, num_rows: int, num_columns: int
) -> str:
  """Get the sheet cell range in the string format.

  Args:
    begin_row: The begin row, in integer format.
    begin_column: The begin column, in string format.
    num_rows: Number of rows.
    num_columns: Number of columns.

  Return:
    The range cell in the string format.
  """
  end_row = begin_row + num_rows - 1
  end_column = chr(ord(begin_column) + num_columns - 1)
  return f'{begin_column}{begin_row}:{end_column}{end_row}'


def _write_compare_info(
    sheet: gspread.Worksheet, report_names: List[str]
) -> None:
  """Write the compare information to a worksheet.

  Args:
    sheet: The object to worksheet for writing.
    report_names: A list of cts report names.
  """
  sheet.update_title('Test Info')
  build_info = []
  for i, name in enumerate(report_names):
    build_info.append([f'Build {i}', name])
  sheet.update(build_info)


def _write_compare_details(
    sheet: gspread.Worksheet, compare_results: List[List[str]], start_row: int
) -> None:
  """Write the detailed comparison result to a worksheet.

  Args:
    sheet: The object to worksheet for writing.
    compare_results: A list of comparison results.
    start_row: The starting row for writing comparison results.
  """
  curr_module = 'None'
  curr_row = start_row
  module_header_row = start_row
  rows_content = []
  module_header_formats = []

  num_reports = len(compare_results[0]) - _NUM_OF_INFO_COLUMNS
  module_failures = [0] * num_reports
  for row_index, row_values in enumerate(compare_results):
    module_name, abi, test_class, test_item, *test_statuses = row_values
    module_end = ((row_index == len(compare_results) - 1) or
                  (module_name != compare_results[row_index + 1][0]))

    # Module changes, need a new header row.
    if module_name != curr_module:
      module_with_abi = (module_name if abi == constant.ABI_IGNORED
                         else f'{module_name} [{abi}]')
      rows_content.append([module_with_abi, ''] + [''] * num_reports)
      module_header_row = len(rows_content)
      header_cell = _get_range_cell(
          begin_row=curr_row, begin_column='A',
          num_rows=1, num_columns=len(rows_content[0]))
      module_header_formats.append({
          'range': header_cell,
          'format': _MODULE_HEADER_FORMAT,
      })
      curr_row += 1

    curr_module = module_name
    for i, status in enumerate(test_statuses):
      test_statuses[i] = '-' if status == 'pass' else status.upper()
      if test_statuses[i] not in ['-', 'ASSUMPTION_FAILURE', 'NULL']:
        module_failures[i] += 1
    rows_content.append([test_class, test_item] + test_statuses)
    curr_row += 1

    # Module ends, update number of failed items in the header.
    if module_end:
      for index, count in enumerate(module_failures):
        # The first two columns are for module info.
        rows_content[module_header_row - 1][index + 2] = f'Failed: {count}'
      module_failures = [0] * num_reports

  if rows_content and module_header_formats:
    content_cell = _get_range_cell(
        begin_row=start_row, begin_column='A',
        num_rows=len(rows_content), num_columns=len(rows_content[0]))
    sheet.update(content_cell, rows_content)
    sheet.batch_format(module_header_formats)


def main():
  args = _parse_args()

  # Get the comparison result
  report_names, compare_results = _read_csv(args.compared_result)

  # Create a google spread sheet
  sheets = _create_spread_sheet(args.sheet_name, args.credentials_dir)

  # Write test info to the fist worksheet
  _write_compare_info(sheets.sheet1, report_names)

  # Write comapre details to the second worksheet
  # Limit the rows to len(compare_results) * 2 because we need module headers
  detail_sheet = sheets.add_worksheet(
      title='Detailed Comparison',
      rows=len(compare_results) * 2,
      cols=len(compare_results[0]))

  # Format the first row
  row_header = ['Test Class', 'Test Item'] + report_names
  cell = _get_range_cell(
      begin_row=1, begin_column='A', num_rows=1, num_columns=len(row_header))
  detail_sheet.update(cell, [row_header])
  detail_sheet.format(cell, _SHEET_HEADER_FORMAT)

  # write details to the worksheet, starting at the second row
  _write_compare_details(detail_sheet, compare_results, 2)


if __name__ == '__main__':
  main()
