#!/usr/bin/env python3

# This file is a part of the CaosDB Project.
#
# Copyright (C) 2021 IndiScale GmbH <www.indiscale.com>
# Copyright (C) 2021 Henrik tom Wörden <h.tomwoerden@indiscale.com>
# Copyright (C) 2021 Alexander Kreft <akreft@trineo.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

import unittest
from os import name

import caosdb as db
from caosadvancedtools.structure_mapping import (EntityMapping,
                                                 collect_existing_structure)
from caosdb.common import datatype


class structureMappingTest(unittest.TestCase):
    def test_Entitymapping(self):
        ex = db.Record(id=100)  # existing Record
        tar = db.Record()  # target Record
        em = EntityMapping()
        em.add(tar, ex)

        for key, val in em.to_existing.items():
            self.assertEqual(key, tar._cuid)
            self.assertEqual(val, ex)

        for key, val in em.to_target.items():
            self.assertEqual(key, ex.id)
            self.assertEqual(val, tar)

    def test_collect_existing_structure(self):
        emap = EntityMapping()
        reca1 = db.Record(name="Animals", id=100)
        reca2 = db.Record(name="Dogs", id=200)
        reca3 = db.Record(name="Husky", id=300)
        reca1.add_property(id=101, name="Cute Animals", datatype=db.REFERENCE, value=reca2)
        reca2.add_property(id=201, name="Cute Dogs", datatype=db.REFERENCE, value=reca3)

        recb1 = db.Record(name="Animals")
        recb2 = db.Record(name="Dogs")
        recb3 = db.Record(name="Husky")
        recb1.add_property(name="Cute Animals", datatype=db.REFERENCE, value=recb2)
        recb2.add_property(name="Cute Dogs", datatype=db.REFERENCE, value=recb3)

        collect_existing_structure(recb1, reca1, emap)

        # Test if the two dicts of the entity mapping correctly depend on each other

        for i in emap.to_existing.keys():
            self.assertEqual(i, emap.to_target[emap.to_existing[i].id]._cuid)

        for j in emap.to_target.keys():
            self.assertEqual(j, emap.to_existing[emap.to_target[j]._cuid].id)

        # Test if only the right Properties are in the dicts
        self.assertTrue((reca2 in emap.to_existing.values()) and
                        (reca3 in emap.to_existing.values()) and
                        (reca1 not in emap.to_existing.values()))
        self.assertTrue((recb2 in emap.to_target.values()) and
                        (recb3 in emap.to_target.values()) and
                        (recb1 not in emap.to_target.values()))

        # Test the correct assignment of the properties
        self.assertTrue(reca2 is emap.to_existing[recb2._cuid])
        self.assertTrue(reca3 is emap.to_existing[recb3._cuid])

        self.assertTrue(recb2 is emap.to_target[reca2.id])
        self.assertTrue(recb3 is emap.to_target[reca3.id])

        """Test with one additional Property and Properties, which are not Records"""
        emap2 = EntityMapping()
        recc1 = db.Record(name="Transportation", id=100)
        recc2 = db.Record(name="Cars", id=200)
        recc3 = db.Record(name="Volvo", id=300)
        recc1.add_property(id=101, name="Type", datatype=db.REFERENCE, value=recc2)
        recc2.add_property(id=201, name="Brand", datatype=db.REFERENCE, value=recc3)
        # other datatypes
        recc3.add_property(id=301, name="max_speed", value=200.2, datatype=db.DOUBLE)
        recc3.add_property(id=302, name="doors", value=3, datatype=db.INTEGER)

        recd1 = db.Record(name="Transportation")
        recd2 = db.Record(name="Cars")
        recd3 = db.Record(name="Volvo")
        recd4 = db.Record(name="VW")
        recd1.add_property(name="Type", datatype=db.REFERENCE, value=recd2)
        recd2.add_property(name="Brand", datatype=db.REFERENCE, value=recd3)
        # additional Property
        recd2.add_property(name="Another Brand", datatype=db.REFERENCE, value=recd4)
        # other datatypes
        recd3.add_property(name="max_speed", value=200.2, datatype=db.DOUBLE)
        recd3.add_property(name="doors", value=3, datatype=db.INTEGER)
        recd4.add_property(name="max_speed", value=210.4, datatype=db.DOUBLE)
        recd4.add_property(name="doors", value=5, datatype=db.INTEGER)
        recd4.add_property(name="Warp engine", value=None)

        collect_existing_structure(recd1, recc1, emap2)

        # Test the correct assignment of the properties
        self.assertTrue(recc2 is emap2.to_existing[recd2._cuid])
        self.assertTrue(recc3 is emap2.to_existing[recd3._cuid])

        self.assertTrue(recd2 is emap2.to_target[recc2.id])
        self.assertTrue(recd3 is emap2.to_target[recc3.id])

        """ Test, if the Record `Cars` in `target_structure` have one additional Property """
        # Test existing structure
        self.assertEqual(len(recc2.get_properties()), 1)  # number of properties stay unchanged
        self.assertEqual(len(recd2.get_properties()), 2)  # number of properties stay unchanged

        for prop_record, prop_em in zip(recc2.get_properties(), recd2.get_properties()):
            self.assertTrue(prop_record.value is emap2.to_existing[prop_em.value._cuid])

        # Test target structure
        self.assertEqual(len(recc3.get_properties()), 2)  # number of properties stay unchanged
        self.assertEqual(len(recd3.get_properties()), 2)  # number of properties stay unchanged

        """ Test if the Properties that are not References show up in the entity map """
        for rec_existing, rec_target in zip(emap2.to_existing.values(), emap2.to_target.values()):
            self.assertTrue(isinstance(rec_existing, db.Record))
            self.assertTrue(isinstance(rec_target, db.Record))
