# Copyright 2024 Georges Racinet <georges.racinet@cloudcrane.io>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
#
# SPDX-License-Identifier: GPL-2.0-or-later
import re

from mercurial import (
    error as hgerror,
)

from .errors import (
    MercurialPathNotFound,
    MercurialChangesetNotFound,
)
from .oid import extract_blob_oid
from .stub.analysis_pb2 import (
    CheckBlobsGeneratedRequest,
)


def frozen_bytes_set(it):
    return frozenset(s.encode('ascii') for s in it)


# compare go-enry/data/generated.go
GENERATED_FILE_PATH_REGEXPS = [
    re.compile(b'|'.join((
        rb'(^Pods|/Pods)/',  # Cocoa pods
        rb'(^|/)Carthage/Build/',  # Carthage build
        rb'\.feature\.css$',  # Generated NET specflow feature file
        rb'node_modules/',  # Node modules
        # Go vendor
        rb'vendor/([-0-9A-Za-z]+\.)+(com|edu|gov|in|me|net|org|fm|io)',
        rb'(Gopkg|glide).lock$',  # Go lock
        rb'(^|/)(\w+\.)?esy.lock$`',  # Esy lock
        rb'npm-shrinkwrap.json$',
        rb'package-lock.json$',  # NPM package lock
        rb'(^|/)\.pnp\.(c|m)?js$',  # Yarn plugnplay
        rb'Godeps/',
        rb'composer.lock$',
        rb'.\.zep\.(?:c|h|php)$',  # Generated by zephir
        rb'Cargo.lock$',
        rb'Pipfile.lock$',  # Pipenv lock
        rb'__generated__/',  # GraphQL relay
        rb'\.(js|css)\.map$',  # Source map
    ))),
    re.compile(b'|'.join((
        rb'\.designer\.(cs|vb)$',  # NET designer file
    )), re.IGNORECASE),
]

# compare go-enry/data/generated.go
GENERATED_FILE_EXTENSIONS = frozen_bytes_set((
    # Xcode
    'nib',
    'xcworkspacedata',
    'xcuserstate',
    # TODO complete the list
))

GENERATED_LINE_RX = re.compile(
    b'|'.join((
        # Source Map
        rb'\A{"version":\d+',
        rb'\A/\*\*Begin line maps\. \*\*/{',
        # Generated NetDoc  TODO should be only for .xml files
        rb'\A.*$.*<doc>.*(\r)?\n.*<assembly>(\r)?\n.*</doc>(\r)?\n.*\Z',
        # PEG.js
        rb'^(?:[^/]|/[^\*])*/\*(?:[^\*]|\*[^/])*Generated by PEG.js',
        # Generated Go (actually our version is more general, but anything
        # matching this regexp is looking for trouble!
        rb'Code generated by',
        # Protocol Buffers, general case
        b"Generated by the protocol buffer compiler.  DO NOT EDIT!",
        # Protocol Buffers, JavaScript (should be only line 6 and crossed with
        # extension, but given the content, matching this is acceptable
        # TODO unless we are looking at a code generator!
        b"GENERATED CODE -- DO NOT EDIT!",
        # Apache Thrift
        b"Autogenerated by Thrift Compiler",
        # JNI header (again more general than go-enry, because we don't want
        # to assert it to be JNI, just that it is generated)
        rb"/\* DO NOT EDIT THIS FILE - it is machine generated \*/",
        # VCR Cassette TODO should be for .yml files only
        rb"\A.*(\r)?\n.*recorded_with: VCR",
        # Compiled Cython TODO should be .c files only
        rb"\A.*Generated by Cython",
        # (Fortran) module
        rb"\A.*(PCBNEW-LibModule-V|GFORTRAN module version ')",
        # Racc
        rb"# This file is automatically generated by Racc",
        # JFlex
        rb"/\* The following code was generated by JFlex ",
        # Grammar Kit
        rb"// This is a generated file. Not intended for manual editing\.",
        # Roxygen 2
        rb"% Generated by roxygen2: do not edit by hand",
        # Jison
        rb"/\* (parser generated by jison |generated by jison-lex )",
        # gRPC C++
        rb"// Generated by the gRPC",
        # Dart
        rb"generated code\W{2,3}do not modify",
        # Perl's PPPort
        rb"Automatically created by Devel::PPPort",
        # GIMP
        rb'/\* GIMP [a-zA-Z0-9\- ]+ C\-Source image dump \(.+?\.c\) \*/',
        (rb'/\*  GIMP header image file format '
         rb'\([a-zA-Z0-9\- ]+\)\: .+?\.h  \*/'),
        # Visual Studio 6
        rb'# Microsoft Developer Studio Generated Build File',
        # Haxe
        rb"Generated by Haxe",
        # Jooq
        rb"This file is generated by jOOQ\.",
    )),
    re.MULTILINE
)

# TODO isMinifiedFile, hasSourceMapReference, isCompiledCoffeeScript,
# isGeneratedPostScript,
# isGeneratedUnity3DMeta (extension restrictions should really apply)
# isGeneratedGameMakerStudio, isGeneratedHTML


def is_blob_generated(repo, blob: CheckBlobsGeneratedRequest.Blob):
    """"Detect if a blob is made of generated data."""
    # For now the Rails application sends revisions in `commit_id:path`
    # format, so close to HGitaly blob oids (!). Probably upstream does not
    # want to use actual Git blob oids, but Gitaly does support them
    colon_split = blob.revision.split(b':', 1)
    if len(colon_split) == 2:
        csid, rpath = colon_split
    else:  # direct oid
        csid, rpath = extract_blob_oid(repo, blob.revision.decode('utf-8'))

    if csid is None or rpath is None:
        raise ValueError("Invalid blob oid")

    extension = rpath.rsplit(b'.', 1)[-1]
    if extension in GENERATED_FILE_EXTENSIONS:
        return True

    if any(rx.search(rpath) is not None
           for rx in GENERATED_FILE_PATH_REGEXPS):
        return True

    try:
        changeset = repo[csid]
    except hgerror.RepoLookupError:
        raise MercurialChangesetNotFound(csid)

    try:
        filectx = changeset.filectx(rpath)
    except KeyError:
        raise MercurialPathNotFound(rpath)

    return GENERATED_LINE_RX.search(filectx.data()) is not None
