#! /usr/bin/env python3
"""
Gramps - a GTK+/GNOME based genealogy program

Copyright (c) 2016 Gramps Development Team

This program 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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
"""

import unittest
import os
import difflib
from unittest.mock import patch
from time import localtime, strptime
import tempfile

from gramps.test.test_util import Gramps
from gramps.gen.const import DATA_DIR
from gramps.gen.datehandler import set_format
from gramps.gen.user import User
from gramps.gen.utils.config import config

TREE_NAME = "Test_exporttest"
TEST_DIR = os.path.abspath(os.path.join(DATA_DIR, "tests"))


def mock_localtime(*args):
    """
    Mock up a dummy to replace the varying 'time string results'
    """
    return strptime("25 Dec 1999", "%d %b %Y")


def call(*args):
    """Call Gramps to perform the action with out and err captured"""
    # print("call:", args)
    gramps = Gramps(user=User())
    out, err = gramps.run(*args)
    # print("out:", out, "err:", err)
    return out, err


def do_it(srcfile, tstfile, dfilter=None):
    """based on tstfile, prepare an result export and compare with
    expected.
    """
    tst_file = os.path.join(TEST_DIR, srcfile)
    expect_file = os.path.join(TEST_DIR, tstfile)
    with tempfile.TemporaryDirectory() as tmpdirname:
        result_file = os.path.join(tmpdirname, tstfile)
        err = call(
            "-C", TREE_NAME, "-q", "--import", tst_file, "--export", result_file
        )[1]
        if "Cleaning up." not in err:
            return "Export failed, no 'Cleaning up.'"
        msg = compare(expect_file, result_file, dfilter)
        if not msg:
            # we will leave the result_file in place if there was an error.
            try:
                os.remove(result_file)
            except OSError:
                pass
            return
        else:
            return msg


def compare(expect_file, result_file, dfilter=None):
    """This uses the diff library to compare two files"""
    with open(
        expect_file, encoding="utf-8_sig", errors="surrogateescape"
    ) as exp_f, open(
        result_file, encoding="utf-8_sig", errors="surrogateescape"
    ) as res_f:
        diff = difflib.unified_diff(
            exp_f.readlines(), res_f.readlines(), n=2, lineterm="\n"
        )
        msg = ""
        fail = False
        for line in diff:
            if line == "--- \n" or line == "+++ \n":
                continue
            msg += line
            if dfilter:
                fail += dfilter(line)
            else:
                fail = True
        return msg if fail else ""


def gedfilt(line):
    """A filter for Gedcom files.
    This implements a filter that allows some differences to be ignored.
    The differences are not functional, but are related to changes in Gramps
    version, file date/time and filename.
    """

    def get_prev_token(back):
        if back > gedfilt.indx:
            return None
        return gedfilt.prev[gedfilt.indx - back][0]

    # pylint: disable=unsubscriptable-object
    if line.startswith("@@"):
        gedfilt.prev = [None] * 16
        gedfilt.indx = 0
        return False
    retval = True
    diftyp = line[0]
    line = line[1:].partition(" ")
    level = int(line[0])
    token, toss, line = line[2].partition(" ")
    if diftyp == " ":
        # save the line for later if needed to figure out the data element
        gedfilt.prev[gedfilt.indx] = token, level, line
        gedfilt.indx = (gedfilt.indx + 1) % 16
        retval = False
    elif diftyp == "-":
        # save the line for later if needed to figure out the data element
        gedfilt.prev[gedfilt.indx] = token, level, line
        gedfilt.indx = (gedfilt.indx + 1) % 16
        if token == "VERS" and get_prev_token(2) == "SOUR":
            # we must have a header with Gramps version
            retval = False
        elif token == "DATE" and get_prev_token(2) == "NAME":
            # we must have a header with file date
            retval = False
        elif token == "DATE" and get_prev_token(2) == "CHAN\n":
            # probably have a timestamp of last change
            retval = False
        elif token == "TIME" and get_prev_token(2) == "DATE":
            # probably have a header with file time
            retval = False
        elif token == "TIME" and get_prev_token(2) == "CHAN\n":
            # probably have a timestamp of last change
            retval = False
        elif token == "FILE" and line.endswith(".ged\n"):
            # probably have a header with file name
            retval = False
        elif token == "FILE" and "tests" in line:
            # probably have a media with file name
            retval = False
        elif token == "COPR" and "Copyright (c) " in line:
            # probably have a copyright line with year
            retval = False
    else:  # this is an addition
        if token == "VERS" and get_prev_token(1) == "VERS":
            # we must have a header with Gramps version
            retval = False
        elif token == "DATE" and (
            get_prev_token(2) == "NAME" or get_prev_token(3) == "NAME"
        ):
            # we must have a header with file date
            retval = False
        elif token == "DATE" and (
            get_prev_token(3) == "CHAN\n" or get_prev_token(4) == "CHAN\n"
        ):
            # probably have a timestamp of last change
            retval = False
        elif token == "TIME" and (
            get_prev_token(2) == "DATE" or get_prev_token(3) == "DATE"
        ):
            # probably have a header with file time
            retval = False
        elif token == "FILE" and line.endswith(".ged\n"):
            # probably have a header with file name
            retval = False
        elif token == "FILE" and "tests" in line:
            # probably have a media with file name
            retval = False
        elif token == "COPR" and "Copyright (c) " in line:
            # probably have a copyright line with year
            retval = False
    return retval


def vcffilt(line):
    """A filter for VCard files.
    This implements a filter that allows some differences to be ignored.
    The differences are not functional, but are related to changes in Gramps
    version.
    """
    diftyp = line[0]
    if diftyp == "@":
        retval = False
    elif diftyp == " ":
        retval = False
    elif "PRODID:" in line:  # Gramps version is on these lines
        retval = False
    else:
        retval = True
    return retval


class ExportControl(unittest.TestCase):
    """These tests compare various exported files with expected files,
    based on the matching '.gramps' test file as a source.
    As more types of exports are tested, we will need to provide some
    filters for the differences; some types of exports have Gramps versions,
    export dates, file names etc. that don't count as differences.
    """

    def setUp(self):
        config.set("behavior.date-before-range", 50)
        config.set("behavior.date-after-range", 50)
        config.set("behavior.date-about-range", 10)
        self.tearDown()  # removes it if it existed

    #        out, err = self.call("-C", TREE_NAME,
    #                            "--import", example)

    def tearDown(self):
        call("-y", "-q", "--remove", TREE_NAME)

    def test_csv(self):
        """Run a csv export test"""
        set_format(0)  # Use ISO date for test
        config.set("database.backend", "sqlite")
        src_file = "exp_sample_csv.gramps"
        tst_file = "exp_sample_csv.csv"
        msg = do_it(src_file, tst_file)
        if msg:
            self.fail(tst_file + ": " + msg)

    def test_ged(self):
        """Run a Gedcom export test"""
        config.set("preferences.place-auto", True)
        config.set("database.backend", "sqlite")
        src_file = "exp_sample.gramps"
        tst_file = "exp_sample_ged.ged"
        msg = do_it(src_file, tst_file, gedfilt)
        if msg:
            self.fail(tst_file + ": " + msg)

    def test_vcard(self):
        """Run a vcard export test"""
        config.set("preferences.place-auto", True)
        config.set("database.backend", "sqlite")
        src_file = "exp_sample.gramps"
        tst_file = "exp_sample.vcf"
        msg = do_it(src_file, tst_file, vcffilt)
        if msg:
            self.fail(tst_file + ": " + msg)

    @patch("gramps.plugins.export.exportvcalendar.time.localtime", mock_localtime)
    def test_vcs(self):
        """Run a Vcalandar export test"""
        config.set("preferences.place-auto", True)
        config.set("database.backend", "sqlite")
        src_file = "exp_sample.gramps"
        tst_file = "exp_sample.vcs"
        msg = do_it(src_file, tst_file)
        if msg:
            self.fail(tst_file + ": " + msg)

    def test_gw(self):
        """Run a Geneweb export test"""
        config.set("preferences.place-auto", True)
        config.set("database.backend", "sqlite")
        src_file = "exp_sample.gramps"
        tst_file = "exp_sample.gw"
        msg = do_it(src_file, tst_file)
        if msg:
            self.fail(tst_file + ": " + msg)

    def test_wft(self):
        """Run a Web Family Tree export test"""
        set_format(0)  # Use ISO date for test
        config.set("preferences.place-auto", True)
        config.set("database.backend", "sqlite")
        src_file = "exp_sample.gramps"
        tst_file = "exp_sample.wft"
        msg = do_it(src_file, tst_file)
        if msg:
            self.fail(tst_file + ": " + msg)


if __name__ == "__main__":
    unittest.main()
