#!/usr/bin/env python3
############################################################################
#
# MODULE:	r.unpack
# AUTHOR(S):	Hamish Bowman, Otago University, New Zealand
#               Converted to Python by Martin Landa <landa.martin gmail.com>
# PURPOSE:	Unpack up a raster map packed with r.pack
# COPYRIGHT:	(C) 2010-2017 by the GRASS Development Team
#
# 		This program is free software under the GNU General
# 		Public License (>=v2). Read the file COPYING that
# 		comes with GRASS for details.
#
#############################################################################

# %module
# % description: Imports a GRASS GIS specific raster archive file (packed with r.pack) as a raster map
# % keyword: raster
# % keyword: import
# % keyword: copying
# %end
# %option G_OPT_F_BIN_INPUT
# % description: Name of input pack file
# % key_desc: name.pack
# %end
# %option G_OPT_R_OUTPUT
# % description: Name for output raster map (default: taken from input file internals)
# % required: no
# % guisection: Output settings
# %end
# %flag
# % key: o
# % label: Override projection check (use current projects's CRS)
# % description: Assume that the dataset has same coordinate reference system as the current project
# % guisection: Output settings
# %end
# %flag
# % key: p
# % label: Print projection information of input pack file and exit
# % guisection: Print
# %end

import os
import sys
import shutil
import tarfile
import atexit

from grass.script.utils import diff_files, try_rmdir
from grass.script import core as grass


def cleanup():
    try_rmdir(tmp_dir)


def main():
    infile = options["input"]

    global tmp_dir
    tmp_dir = grass.tempdir()
    grass.debug("tmp_dir = {tmpdir}".format(tmpdir=tmp_dir))

    if not os.path.exists(infile):
        grass.fatal(_("File {name} not found.".format(name=infile)))

    gisenv = grass.gisenv()
    mset_dir = os.path.join(
        gisenv["GISDBASE"], gisenv["LOCATION_NAME"], gisenv["MAPSET"]
    )
    input_base = os.path.basename(infile)
    shutil.copyfile(infile, os.path.join(tmp_dir, input_base))
    os.chdir(tmp_dir)
    tar = tarfile.TarFile.open(name=input_base, mode="r")
    try:
        data_names = [
            tarinfo.name for tarinfo in tar.getmembers() if "/" not in tarinfo.name
        ]
    except Exception:
        grass.fatal(_("Pack file unreadable"))

    if flags["p"]:
        # print proj info and exit
        try:
            for fname in ["PROJ_INFO", "PROJ_UNITS"]:
                f = tar.extractfile("{}/{}".format(data_names[0], fname))
                sys.stdout.write(f.read().decode())
        except KeyError:
            grass.fatal(_("Pack file unreadable: file '{}' missing".format(fname)))
        tar.close()

        return 0

    if options["output"]:
        map_name = options["output"]
    else:
        map_name = data_names[0].split("@")[0]

    gfile = grass.find_file(name=map_name, element="cell", mapset=".")
    if gfile["file"]:
        if os.environ.get("GRASS_OVERWRITE", "0") != "1":
            grass.fatal(_("Raster map <{name}> already exists".format(name=map_name)))
        else:
            grass.warning(
                _(
                    "Raster map <{name}> already exists and will be overwritten".format(
                        name=map_name
                    )
                )
            )

    # extract data
    # Extraction filters were added in Python 3.12,
    # and backported to 3.8.17, 3.9.17, 3.10.12, and 3.11.4
    # See https://docs.python.org/3.12/library/tarfile.html#tarfile-extraction-filter
    # and https://peps.python.org/pep-0706/
    # In Python 3.12, using `filter=None` triggers a DepreciationWarning,
    # and in Python 3.14, `filter='data'` will be the default
    if hasattr(tarfile, "data_filter"):
        tar.extractall(filter="data")
    else:
        # Remove this when no longer needed
        grass.warning(_("Extracting may be unsafe; consider updating Python"))
        tar.extractall()
    tar.close()
    os.chdir(data_names[0])

    if os.path.exists("cell"):
        pass
    elif os.path.exists("coor"):
        grass.fatal(
            _(
                "This GRASS GIS pack file contains vector data. Use "
                "v.unpack to unpack <{name}>".format(name=map_name)
            )
        )
    else:
        grass.fatal(_("Pack file unreadable"))

    # check projection compatibility in a rather crappy way

    if flags["o"]:
        grass.warning(_("Overriding projection check (using current project's CRS)."))

    else:
        diff_result_1 = diff_result_2 = None

        proj_info_file_1 = "PROJ_INFO"
        proj_info_file_2 = os.path.join(mset_dir, "..", "PERMANENT", "PROJ_INFO")

        skip_projection_check = False
        if not os.path.exists(proj_info_file_1):
            if os.path.exists(proj_info_file_2):
                grass.fatal(
                    _(
                        "PROJ_INFO file is missing, unpack raster map in XY "
                        "(unprojected) project."
                    )
                )
            skip_projection_check = True  # XY location

        if not skip_projection_check:
            if not grass.compare_key_value_text_files(
                filename_a=proj_info_file_1, filename_b=proj_info_file_2, proj=True
            ):
                diff_result_1 = diff_files(proj_info_file_1, proj_info_file_2)

            proj_units_file_1 = "PROJ_UNITS"
            proj_units_file_2 = os.path.join(mset_dir, "..", "PERMANENT", "PROJ_UNITS")

            if not grass.compare_key_value_text_files(
                filename_a=proj_units_file_1, filename_b=proj_units_file_2, units=True
            ):
                diff_result_2 = diff_files(proj_units_file_1, proj_units_file_2)

            if diff_result_1 or diff_result_2:
                if diff_result_1:
                    grass.warning(
                        _(
                            "Difference between PROJ_INFO file of packed map "
                            "and of current project:\n{diff}"
                        ).format(diff="".join(diff_result_1))
                    )
                if diff_result_2:
                    grass.warning(
                        _(
                            "Difference between PROJ_UNITS file of packed map "
                            "and of current project:\n{diff}"
                        ).format(diff="".join(diff_result_2))
                    )
                grass.fatal(
                    _(
                        "Coordinate reference system of dataset does"
                        " not appear to match current project."
                        " In case of no significant differences in the CRS definitions,"
                        " use the -o flag to ignore them and use"
                        " current project definition."
                    )
                )

    maps = []
    vrt_file = None
    for index, data in enumerate(data_names):
        if index > 0:
            map_name = data

        for element in ["cats", "cell", "cellhd", "cell_misc", "colr", "fcell", "hist"]:
            src_path = os.path.join(tmp_dir, data, element)
            if not os.path.exists(src_path):
                continue

            path = os.path.join(mset_dir, element)
            if not os.path.exists(path):
                os.mkdir(path)

            if element == "cell_misc":
                if index > 0:
                    maps.append(
                        "{map_name}@{mapset}".format(
                            map_name=map_name,
                            mapset=gisenv["MAPSET"],
                        ),
                    )

                path = os.path.join(
                    mset_dir,
                    element,
                    map_name,
                )
                if index == 0:
                    vrt_file = os.path.join(path, "vrt")

                if os.path.exists(path):
                    shutil.rmtree(path)
                shutil.copytree(src_path, path)
            else:
                shutil.copyfile(
                    src_path,
                    os.path.join(mset_dir, element, map_name),
                )

    # Update vrt file
    if maps:
        if vrt_file and os.path.exists(vrt_file):
            files = "\n".join(maps)
            with open(vrt_file, "w") as f:
                f.write(files)

    grass.message(_("Raster map <{name}> unpacked".format(name=map_name)))


if __name__ == "__main__":
    options, flags = grass.parser()
    atexit.register(cleanup)
    sys.exit(main())
