#!/usr/bin/python3

# Copyright (C) 2020 Red Hat
#
# This file is part of os-autoinst-distri-fedora.
#
# os-autoinst-distri-fedora is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 2 of
# the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Author: Adam Williamson <awilliam@redhat.com>

"""This is a check script which checks for unused needles. If none of
the tags a needle declares is referenced in the tests, it is
considered unused.
"""

import glob
import json
import os
import re
import sys

NEEDLEPATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "needles")
TESTSPATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "tests")
LIBPATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "lib")
# these don't account for escaping, but I don't think we're ever going
# to have an escaped quotation mark in a needle tag
DOUBLEQUOTERE = re.compile('"(.*?)"')
SINGLEQUOTERE = re.compile("'(.*?)'")

# first we're gonna build a big list of all string literals
testpaths = glob.glob(f"{TESTSPATH}/**/*.pm", recursive=True)
testpaths.extend(glob.glob(f"{LIBPATH}/**/*.pm", recursive=True))
testliterals = []
for testpath in testpaths:
    # skip if it's a symlink
    if os.path.islink(testpath):
        continue
    # otherwise, scan it for string literals
    with open(testpath, "r") as testfh:
        testtext = testfh.read()
    for match in DOUBLEQUOTERE.finditer(testtext):
        testliterals.append(match[1])
    for match in SINGLEQUOTERE.finditer(testtext):
        testliterals.append(match[1])

# now let's do some whitelisting, for awkward cases where we know that
# we concatenate string literals and stuff
# versioned backgrounds and release IDs
for rel in range(30, 100):
    testliterals.append(f"{rel}_background")
    testliterals.append(f"version_{rel}_ident")
# anaconda id needles, using tell_source
for source in ("workstation", "generic", "server"):
    testliterals.append(f"leftbar_{source}")
    testliterals.append(f"topbar_{source}")
# keyboard layout switching, using desktop_switch_layout
for environment in ("anaconda", "gnome"):
    for layout in ("native", "ascii"):
        testliterals.append(f"{environment}_layout_{layout}")
# package set selection, using get_var('PACKAGE_SET')
for pkgset in ("kde", "workstation", "minimal"):
    testliterals.append(f"anaconda_{pkgset}_highlighted")
    testliterals.append(f"anaconda_{pkgset}_selected")
# desktop_login stuff
for user in ("jack", "jim"):
    testliterals.append(f"login_{user}")
    testliterals.append(f"user_confirm_{user}")
# partitioning stuff, there's a bunch of this, all in anaconda.pm
# multiple things use this
for part in ("swap", "root", "efi", "boot"):
    testliterals.append(f"anaconda_part_select_{part}")
    testliterals.append(f"anaconda_blivet_part_inactive_{part}")
# select_disks
for num in range(1, 10):
    testliterals.append(f"anaconda_install_destination_select_disk_{num}")
# custom_scheme_select
for scheme in ("standard", "lvmthin", "btrfs"):
    testliterals.append(f"anaconda_part_scheme_{scheme}")
# custom_blivet_add_partition
for dtype in ("lvm", "lvmthin", "raid"):
    testliterals.append(f"anaconda_blivet_part_devicetype_{dtype}")
for fsys in ("ext3", "ext4", "xfs", "btrfs", "ppc_prep_boot", "swap", "efi_filesystem"):
    testliterals.append(f"anaconda_blivet_part_fs_{fsys}")
    testliterals.append(f"anaconda_blivet_part_fs_{fsys}_selected")
# this is variable-y in custom_change_type but we only actually have
# one value
testliterals.append("anaconda_part_device_type_raid")
# custom_change_fs
for fsys in ("ext3", "xfs"):
    testliterals.append(f"anaconda_part_fs_{fsys}")
    testliterals.append(f"anaconda_part_fs_{fsys}_selected")
# variable-y in custom_change_device but we only have one value
testliterals.append("anaconda_part_device_sda")

# retcode tracker
ret = 0

# now let's scan our needles
unused = []
noimg = []
noneedle = []

needlepaths = glob.glob(f"{NEEDLEPATH}/**/*.json", recursive=True)
for needlepath in needlepaths:
    # check we have a matching image file
    imgpath = needlepath.replace(".json", ".png")
    if not os.path.exists(imgpath):
        noimg.append(needlepath)
    with open(needlepath, "r") as needlefh:
        needlejson = json.load(needlefh)
    if any(tag in testliterals for tag in needlejson["tags"]):
        continue
    unused.append(needlepath)

# reverse check, for images without a needle file
imgpaths = glob.glob(f"{NEEDLEPATH}/**/*.png", recursive=True)
for imgpath in imgpaths:
    needlepath = imgpath.replace(".png", ".json")
    if not os.path.exists(needlepath):
        noneedle.append(imgpath)

if unused:
    ret += 1
    print("Unused needle(s) found!")
    for needle in unused:
        print(needle)

if noimg:
    ret += 2
    print("Needle(s) without image(s) found!")
    for needle in noimg:
        print(needle)

if noneedle:
    ret += 4
    print("Image(s) without needle(s) found!")
    for img in noneedle:
        print(img)

sys.exit(ret)