# Copyright (C) 2009 Canonical Ltd
#
# 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

"""Tests for eol conversion."""

import sys
from io import BytesIO

from ... import rules, status
from ...workingtree import WorkingTree
from .. import TestSkipped
from . import TestCaseWithWorkingTree

# Sample files
_sample_text = b"""hello\nworld\r\n"""
_sample_text_on_win = b"""hello\r\nworld\r\n"""
_sample_text_on_unix = b"""hello\nworld\n"""
_sample_binary = b"""hello\nworld\r\n\x00"""
_sample_clean_lf = _sample_text_on_unix
_sample_clean_crlf = _sample_text_on_win


# Lists of formats for each storage policy
_LF_IN_REPO = ["native", "lf", "crlf"]
_CRLF_IN_REPO = ["{}-with-crlf-in-repo".format(f) for f in _LF_IN_REPO]


class TestEolConversion(TestCaseWithWorkingTree):
    def setUp(self):
        # formats that don't support content filtering can skip these tests
        fmt = self.workingtree_format
        f = getattr(fmt, "supports_content_filtering")
        if f is None:
            raise TestSkipped(
                "format %s doesn't declare whether it "
                "supports content filtering, assuming not" % fmt
            )
        if not f():
            raise TestSkipped("format %s doesn't support content filtering" % fmt)
        super().setUp()

    def patch_rules_searcher(self, eol):
        """Patch in a custom rules searcher with a given eol setting."""
        if eol is None:
            WorkingTree._get_rules_searcher = self.real_rules_searcher
        else:

            def custom_eol_rules_searcher(tree, default_searcher):
                return rules._IniBasedRulesSearcher(
                    [
                        "[name *]\n",
                        "eol=%s\n" % eol,
                    ]
                )

            WorkingTree._get_rules_searcher = custom_eol_rules_searcher

    def prepare_tree(self, content, eol=None):
        """Prepare a working tree and commit some content."""
        self.real_rules_searcher = self.overrideAttr(WorkingTree, "_get_rules_searcher")
        self.patch_rules_searcher(eol)
        t = self.make_branch_and_tree("tree1")
        self.build_tree_contents([("tree1/file1", content)])
        t.add("file1")
        t.commit("add file1")
        basis = t.basis_tree()
        basis.lock_read()
        self.addCleanup(basis.unlock)
        return t, basis

    def assertNewContentForSetting(
        self, wt, eol, expected_unix, expected_win, roundtrip
    ):
        """Clone a working tree and check the convenience content.

        If roundtrip is True, status and commit should see no changes.
        """
        if expected_win is None:
            expected_win = expected_unix
        self.patch_rules_searcher(eol)
        wt2 = wt.controldir.sprout("tree-%s" % eol).open_workingtree()
        # To see exactly what got written to disk, we need an unfiltered read
        with wt2.get_file("file1", filtered=False) as f:
            content = f.read()
        if sys.platform == "win32":
            self.assertEqual(expected_win, content)
        else:
            self.assertEqual(expected_unix, content)
        # Confirm that status thinks nothing has changed if the text roundtrips
        if roundtrip:
            status_io = BytesIO()
            status.show_tree_status(wt2, to_file=status_io)
            self.assertEqual(b"", status_io.getvalue())

    def assertContent(
        self, wt, basis, expected_raw, expected_unix, expected_win, roundtrip_to=None
    ):
        """Check the committed content and content in cloned trees.

        :param roundtrip_to: the set of formats (excluding exact) we
          can round-trip to or None for all
        """
        with basis.get_file("file1") as f:
            basis_content = f.read()
        self.assertEqual(expected_raw, basis_content)

        # No setting and exact should always roundtrip
        self.assertNewContentForSetting(
            wt, None, expected_raw, expected_raw, roundtrip=True
        )
        self.assertNewContentForSetting(
            wt, "exact", expected_raw, expected_raw, roundtrip=True
        )

        # Roundtripping is otherwise dependent on whether the original
        # text is clean - mixed line endings will prevent it. It also
        # depends on whether the format in the repository is being changed.
        if roundtrip_to is None:
            roundtrip_to = _LF_IN_REPO + _CRLF_IN_REPO
        self.assertNewContentForSetting(
            wt, "native", expected_unix, expected_win, "native" in roundtrip_to
        )
        self.assertNewContentForSetting(
            wt, "lf", expected_unix, expected_unix, "lf" in roundtrip_to
        )
        self.assertNewContentForSetting(
            wt, "crlf", expected_win, expected_win, "crlf" in roundtrip_to
        )
        self.assertNewContentForSetting(
            wt,
            "native-with-crlf-in-repo",
            expected_unix,
            expected_win,
            "native-with-crlf-in-repo" in roundtrip_to,
        )
        self.assertNewContentForSetting(
            wt,
            "lf-with-crlf-in-repo",
            expected_unix,
            expected_unix,
            "lf-with-crlf-in-repo" in roundtrip_to,
        )
        self.assertNewContentForSetting(
            wt,
            "crlf-with-crlf-in-repo",
            expected_win,
            expected_win,
            "crlf-with-crlf-in-repo" in roundtrip_to,
        )

    # Test binary files. These always roundtrip.

    def test_eol_no_rules_binary(self):
        wt, basis = self.prepare_tree(_sample_binary)
        self.assertContent(wt, basis, _sample_binary, _sample_binary, _sample_binary)

    def test_eol_exact_binary(self):
        wt, basis = self.prepare_tree(_sample_binary, eol="exact")
        self.assertContent(wt, basis, _sample_binary, _sample_binary, _sample_binary)

    def test_eol_native_binary(self):
        wt, basis = self.prepare_tree(_sample_binary, eol="native")
        self.assertContent(wt, basis, _sample_binary, _sample_binary, _sample_binary)

    def test_eol_lf_binary(self):
        wt, basis = self.prepare_tree(_sample_binary, eol="lf")
        self.assertContent(wt, basis, _sample_binary, _sample_binary, _sample_binary)

    def test_eol_crlf_binary(self):
        wt, basis = self.prepare_tree(_sample_binary, eol="crlf")
        self.assertContent(wt, basis, _sample_binary, _sample_binary, _sample_binary)

    def test_eol_native_with_crlf_in_repo_binary(self):
        wt, basis = self.prepare_tree(_sample_binary, eol="native-with-crlf-in-repo")
        self.assertContent(wt, basis, _sample_binary, _sample_binary, _sample_binary)

    def test_eol_lf_with_crlf_in_repo_binary(self):
        wt, basis = self.prepare_tree(_sample_binary, eol="lf-with-crlf-in-repo")
        self.assertContent(wt, basis, _sample_binary, _sample_binary, _sample_binary)

    def test_eol_crlf_with_crlf_in_repo_binary(self):
        wt, basis = self.prepare_tree(_sample_binary, eol="crlf-with-crlf-in-repo")
        self.assertContent(wt, basis, _sample_binary, _sample_binary, _sample_binary)

    # Test text with mixed line endings ("dirty text").
    # This doesn't roundtrip so status always thinks something has changed.

    def test_eol_no_rules_dirty(self):
        wt, basis = self.prepare_tree(_sample_text)
        self.assertContent(
            wt,
            basis,
            _sample_text,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=[],
        )

    def test_eol_exact_dirty(self):
        wt, basis = self.prepare_tree(_sample_text, eol="exact")
        self.assertContent(
            wt,
            basis,
            _sample_text,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=[],
        )

    def test_eol_native_dirty(self):
        wt, basis = self.prepare_tree(_sample_text, eol="native")
        self.assertContent(
            wt,
            basis,
            _sample_text_on_unix,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=[],
        )

    def test_eol_lf_dirty(self):
        wt, basis = self.prepare_tree(_sample_text, eol="lf")
        self.assertContent(
            wt,
            basis,
            _sample_text_on_unix,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=[],
        )

    def test_eol_crlf_dirty(self):
        wt, basis = self.prepare_tree(_sample_text, eol="crlf")
        self.assertContent(
            wt,
            basis,
            _sample_text_on_unix,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=[],
        )

    def test_eol_native_with_crlf_in_repo_dirty(self):
        wt, basis = self.prepare_tree(_sample_text, eol="native-with-crlf-in-repo")
        self.assertContent(
            wt,
            basis,
            _sample_text_on_win,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=[],
        )

    def test_eol_lf_with_crlf_in_repo_dirty(self):
        wt, basis = self.prepare_tree(_sample_text, eol="lf-with-crlf-in-repo")
        self.assertContent(
            wt,
            basis,
            _sample_text_on_win,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=[],
        )

    def test_eol_crlf_with_crlf_in_repo_dirty(self):
        wt, basis = self.prepare_tree(_sample_text, eol="crlf-with-crlf-in-repo")
        self.assertContent(
            wt,
            basis,
            _sample_text_on_win,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=[],
        )

    # Test text with clean line endings, either always lf or always crlf.
    # This selectively roundtrips (based on what's stored in the repo).

    def test_eol_no_rules_clean_lf(self):
        wt, basis = self.prepare_tree(_sample_clean_lf)
        self.assertContent(
            wt,
            basis,
            _sample_clean_lf,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=_LF_IN_REPO,
        )

    def test_eol_no_rules_clean_crlf(self):
        wt, basis = self.prepare_tree(_sample_clean_crlf)
        self.assertContent(
            wt,
            basis,
            _sample_clean_crlf,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=_CRLF_IN_REPO,
        )

    def test_eol_exact_clean_lf(self):
        wt, basis = self.prepare_tree(_sample_clean_lf, eol="exact")
        self.assertContent(
            wt,
            basis,
            _sample_clean_lf,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=_LF_IN_REPO,
        )

    def test_eol_exact_clean_crlf(self):
        wt, basis = self.prepare_tree(_sample_clean_crlf, eol="exact")
        self.assertContent(
            wt,
            basis,
            _sample_clean_crlf,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=_CRLF_IN_REPO,
        )

    def test_eol_native_clean_lf(self):
        wt, basis = self.prepare_tree(_sample_clean_lf, eol="native")
        self.assertContent(
            wt,
            basis,
            _sample_text_on_unix,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=_LF_IN_REPO,
        )

    def test_eol_native_clean_crlf(self):
        wt, basis = self.prepare_tree(_sample_clean_crlf, eol="native")
        self.assertContent(
            wt,
            basis,
            _sample_text_on_unix,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=_LF_IN_REPO,
        )

    def test_eol_lf_clean_lf(self):
        wt, basis = self.prepare_tree(_sample_clean_lf, eol="lf")
        self.assertContent(
            wt,
            basis,
            _sample_text_on_unix,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=_LF_IN_REPO,
        )

    def test_eol_lf_clean_crlf(self):
        wt, basis = self.prepare_tree(_sample_clean_crlf, eol="lf")
        self.assertContent(
            wt,
            basis,
            _sample_text_on_unix,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=_LF_IN_REPO,
        )

    def test_eol_crlf_clean_lf(self):
        wt, basis = self.prepare_tree(_sample_clean_lf, eol="crlf")
        self.assertContent(
            wt,
            basis,
            _sample_text_on_unix,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=_LF_IN_REPO,
        )

    def test_eol_crlf_clean_crlf(self):
        wt, basis = self.prepare_tree(_sample_clean_crlf, eol="crlf")
        self.assertContent(
            wt,
            basis,
            _sample_text_on_unix,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=_LF_IN_REPO,
        )

    def test_eol_native_with_crlf_in_repo_clean_lf(self):
        wt, basis = self.prepare_tree(_sample_clean_lf, eol="native-with-crlf-in-repo")
        self.assertContent(
            wt,
            basis,
            _sample_text_on_win,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=_CRLF_IN_REPO,
        )

    def test_eol_native_with_crlf_in_repo_clean_crlf(self):
        wt, basis = self.prepare_tree(
            _sample_clean_crlf, eol="native-with-crlf-in-repo"
        )
        self.assertContent(
            wt,
            basis,
            _sample_text_on_win,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=_CRLF_IN_REPO,
        )

    def test_eol_lf_with_crlf_in_repo_clean_lf(self):
        wt, basis = self.prepare_tree(_sample_clean_lf, eol="lf-with-crlf-in-repo")
        self.assertContent(
            wt,
            basis,
            _sample_text_on_win,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=_CRLF_IN_REPO,
        )

    def test_eol_lf_with_crlf_in_repo_clean_crlf(self):
        wt, basis = self.prepare_tree(_sample_clean_crlf, eol="lf-with-crlf-in-repo")
        self.assertContent(
            wt,
            basis,
            _sample_text_on_win,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=_CRLF_IN_REPO,
        )

    def test_eol_crlf_with_crlf_in_repo_clean_lf(self):
        wt, basis = self.prepare_tree(_sample_clean_lf, eol="crlf-with-crlf-in-repo")
        self.assertContent(
            wt,
            basis,
            _sample_text_on_win,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=_CRLF_IN_REPO,
        )

    def test_eol_crlf_with_crlf_in_repo_clean_crlf(self):
        wt, basis = self.prepare_tree(_sample_clean_crlf, eol="crlf-with-crlf-in-repo")
        self.assertContent(
            wt,
            basis,
            _sample_text_on_win,
            _sample_text_on_unix,
            _sample_text_on_win,
            roundtrip_to=_CRLF_IN_REPO,
        )
