#!/usr/bin/env 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.

"""
Define data classes that model SBOMs defined by SPDX. The data classes could be
written out to different formats (tagvalue, JSON, etc) of SPDX with corresponding
writer utilities.

Rrefer to SPDX 2.3 spec: https://spdx.github.io/spdx-spec/v2.3/ and go/android-spdx for details of
fields in each data class.
"""

from dataclasses import dataclass, field
from typing import List
import hashlib

SPDXID_DOC = 'SPDXRef-DOCUMENT'
SPDXID_PRODUCT = 'SPDXRef-PRODUCT'
SPDXID_PLATFORM = 'SPDXRef-PLATFORM'

PACKAGE_NAME_PRODUCT = 'PRODUCT'
PACKAGE_NAME_PLATFORM = 'PLATFORM'

VALUE_NOASSERTION = 'NOASSERTION'
VALUE_NONE = 'NONE'


class PackageExternalRefCategory:
  SECURITY = 'SECURITY'
  PACKAGE_MANAGER = 'PACKAGE-MANAGER'
  PERSISTENT_ID = 'PERSISTENT-ID'
  OTHER = 'OTHER'


class PackageExternalRefType:
  cpe22Type = 'cpe22Type'
  cpe23Type = 'cpe23Type'


@dataclass
class PackageExternalRef:
  category: PackageExternalRefCategory
  type: PackageExternalRefType
  locator: str


@dataclass
class Package:
  name: str
  id: str
  version: str = None
  supplier: str = None
  download_location: str = None
  files_analyzed: bool = False
  verification_code: str = None
  file_ids: List[str] = field(default_factory=list)
  external_refs: List[PackageExternalRef] = field(default_factory=list)


@dataclass
class File:
  id: str
  name: str
  checksum: str


class RelationshipType:
  DESCRIBES = 'DESCRIBES'
  VARIANT_OF = 'VARIANT_OF'
  GENERATED_FROM = 'GENERATED_FROM'
  CONTAINS = 'CONTAINS'
  STATIC_LINK = 'STATIC_LINK'


@dataclass
class Relationship:
  id1: str
  relationship: RelationshipType
  id2: str


@dataclass
class DocumentExternalReference:
  id: str
  uri: str
  checksum: str


@dataclass
class Document:
  name: str
  namespace: str
  id: str = SPDXID_DOC
  describes: str = SPDXID_PRODUCT
  creators: List[str] = field(default_factory=list)
  created: str = None
  external_refs: List[DocumentExternalReference] = field(default_factory=list)
  packages: List[Package] = field(default_factory=list)
  files: List[File] = field(default_factory=list)
  relationships: List[Relationship] = field(default_factory=list)

  def add_external_ref(self, external_ref):
    if not any(external_ref.uri == ref.uri for ref in self.external_refs):
      self.external_refs.append(external_ref)

  def add_package(self, package):
    if not any(package.id == p.id for p in self.packages):
      self.packages.append(package)

  def add_relationship(self, rel):
    if not any(rel.id1 == r.id1 and rel.id2 == r.id2 and rel.relationship == r.relationship
               for r in self.relationships):
      self.relationships.append(rel)

  def generate_packages_verification_code(self):
    for package in self.packages:
      if not package.file_ids:
        continue

      checksums = []
      for file in self.files:
        if file.id in package.file_ids:
          checksums.append(file.checksum)
      checksums.sort()
      h = hashlib.sha1()
      h.update(''.join(checksums).encode(encoding='utf-8'))
      package.verification_code = h.hexdigest()
