#!/usr/bin/env python3

import errno, filecmp, fnmatch, glob, os.path, re, sys
from debian import deb822
from enum import Enum

sys.path.insert(0, "debian/lib/python")

from debian_linux.firmware import FirmwareWhence
from config import Config, pattern_to_re

class DistState(Enum):
    undistributable = 1
    non_free = 2
    free = 3

def is_source_available(section):
    for file_info in section.files.values():
        if not (file_info.source
                or file_info.binary.endswith('.txt')
                or file_info.binary.endswith('.cis')):
            return False
    return True

def check_section(section):
    if section.licence is None:
        # Maybe undistributable
        return DistState.undistributable
    elif re.search(r'^BSD\b'
                   r'|^GPLv2 or OpenIB\.org BSD\b'
                   r'|^Apache-2\.0\b'
                   r'|\bPermission\s+is\s+hereby\s+granted\s+for\s+the\s+'
                   r'distribution\s+of\s+this\s+firmware\s+(?:data|image)\b'
                   r'(?!\s+as\s+part\s+of)'
                   r'|\bRedistribution\s+and\s+use\s+in(?:\s+source\s+and)?'
                   r'\s+binary\s+forms\b'
                   r'|\bPermission\s+is\s+hereby\s+granted\b[^.]+\sto'
                   r'\s+deal\s+in\s+the\s+Software\s+without'
                   r'\s+restriction\b'
                   r'|\bredistributable\s+in\s+binary\s+form\b'
                   r'|\bgrants\s+permission\s+to\s+use\s+and\s+redistribute'
                   r'\s+these\s+firmware\s+files\b',
                   section.licence):
        return (DistState.free if is_source_available(section)
                else DistState.non_free)
    elif re.match(r'^(?:D|Red)istributable\b', section.licence):
        return DistState.non_free
    elif re.match(r'^GPL(?:v[23]|\+)?\b|^Dual GPL(?:v[23])?/', section.licence):
        return (DistState.free if is_source_available(section)
                else DistState.undistributable)
    else:
        # Unrecognised and probably undistributable
        return DistState.undistributable

def main(source_dir='.'):
    config = Config()
    over_dirs = ['debian/config/' + package for
                 package in config['base',]['packages']]
    with open("debian/copyright") as f:
        exclusions = deb822.Deb822(f).get("Files-Excluded", '').strip().split()

    package_file_res = []
    for package in config['base',]['packages']:
        config_entry = config['base', package]
        package_file_res.append(
            ([pattern_to_re(pattern)
              for pattern in config_entry['files']],
             [pattern_to_re(pattern)
              for pattern in config_entry.get('files-exclude', [])])
        )

    for section in FirmwareWhence(open(os.path.join(source_dir, 'WHENCE'))):
        dist_state = check_section(section)
        for file_info in section.files.values():
            if dist_state == DistState.free:
                if not any(fnmatch.fnmatch(file_info.binary, exclusion)
                           for exclusion in exclusions):
                    if any(
                        (any(inc_re.fullmatch(file_info.binary)
                             for inc_re in inc_res)
                         and not any(exc_re.fullmatch(file_info.binary)
                                    for exc_re in exc_res))
                        for inc_res, exc_res in package_file_res
                    ):
                        update_file(source_dir, over_dirs, file_info.binary)
                        for source in file_info.source:
                            update_file(source_dir, over_dirs, source)
                    elif os.path.isfile(file_info.binary):
                        print('I: %s is not included in any binary package' %
                              file_info.binary)
                    else:
                        print('I: %s: could be added' % file_info.binary)
            elif dist_state == DistState.non_free:
                if os.path.isfile(file_info.binary):
                    print('W: %s appears to be non-free' %
                          file_info.binary)
            elif dist_state == DistState.undistributable:
                if os.path.isfile(file_info.binary):
                    print('W: %s appears to be undistributable' %
                          file_info.binary)

def update_file(source_dir, over_dirs, filename):
    source_file = os.path.join(source_dir, filename)
    for over_dir in over_dirs:
        for over_file in ([os.path.join(over_dir, filename)] +
                          glob.glob(os.path.join(over_dir, filename + '-*'))):
            if os.path.isfile(over_file):
                if not filecmp.cmp(source_file, over_file, True):
                    print('I: %s: changed' % filename)
                return

if __name__ == '__main__':
    main(*sys.argv[1:])
