# encoding: utf8
import operator
from django.test import TestCase
from django.db.migrations.optimizer import MigrationOptimizer
from django.db import migrations
from django.db import models


class OptimizerTests(TestCase):
    """
    Tests the migration autodetector.
    """

    def optimize(self, operations):
        """
        Handy shortcut for getting results + number of loops
        """
        optimizer = MigrationOptimizer()
        return optimizer.optimize(operations), optimizer._iterations

    def assertOptimizesTo(self, operations, expected, exact=None, less_than=None):
        result, iterations = self.optimize(operations)
        self.assertEqual(expected, result)
        if exact is not None and iterations != exact:
            raise self.failureException("Optimization did not take exactly %s iterations (it took %s)" % (exact, iterations))
        if less_than is not None and iterations >= less_than:
            raise self.failureException("Optimization did not take less than %s iterations (it took %s)" % (less_than, iterations))

    def test_operation_equality(self):
        """
        Tests the equality operator on lists of operations.
        If this is broken, then the optimizer will get stuck in an
        infinite loop, so it's kind of important.
        """
        self.assertEqual(
            [migrations.DeleteModel("Test")],
            [migrations.DeleteModel("Test")],
        )
        self.assertEqual(
            [migrations.CreateModel("Test", [("name", models.CharField(max_length=255))])],
            [migrations.CreateModel("Test", [("name", models.CharField(max_length=255))])],
        )
        self.assertNotEqual(
            [migrations.CreateModel("Test", [("name", models.CharField(max_length=255))])],
            [migrations.CreateModel("Test", [("name", models.CharField(max_length=100))])],
        )
        self.assertEqual(
            [migrations.AddField("Test", "name", models.CharField(max_length=255))],
            [migrations.AddField("Test", "name", models.CharField(max_length=255))],
        )
        self.assertNotEqual(
            [migrations.AddField("Test", "name", models.CharField(max_length=255))],
            [migrations.AddField("Test", "name", models.CharField(max_length=100))],
        )
        self.assertNotEqual(
            [migrations.AddField("Test", "name", models.CharField(max_length=255))],
            [migrations.AlterField("Test", "name", models.CharField(max_length=255))],
        )

    def test_single(self):
        """
        Tests that the optimizer does nothing on a single operation,
        and that it does it in just one pass.
        """
        self.assertOptimizesTo(
            [migrations.DeleteModel("Foo")],
            [migrations.DeleteModel("Foo")],
            exact = 1,
        )

    def test_create_delete_model(self):
        """
        CreateModel and DeleteModel should collapse into nothing.
        """
        self.assertOptimizesTo(
            [
                migrations.CreateModel("Foo", [("name", models.CharField(max_length=255))]),
                migrations.DeleteModel("Foo"),
            ],
            [],
        )

    def test_create_alter_delete_model(self):
        """
        CreateModel, AlterModelTable, AlterUniqueTogether, and DeleteModel should collapse into nothing.
        """
        self.assertOptimizesTo(
            [
                migrations.CreateModel("Foo", [("name", models.CharField(max_length=255))]),
                migrations.AlterModelTable("Foo", "woohoo"),
                migrations.AlterUniqueTogether("Foo", [["a", "b"]]),
                migrations.DeleteModel("Foo"),
            ],
            [],
        )

    def test_optimize_through_create(self):
        """
        We should be able to optimize away create/delete through a create or delete
        of a different model, but only if the create operation does not mention the model
        at all.
        """
        # These should work
        self.assertOptimizesTo(
            [
                migrations.CreateModel("Foo", [("name", models.CharField(max_length=255))]),
                migrations.CreateModel("Bar", [("size", models.IntegerField())]),
                migrations.DeleteModel("Foo"),
            ],
            [
                migrations.CreateModel("Bar", [("size", models.IntegerField())]),
            ],
        )
        self.assertOptimizesTo(
            [
                migrations.CreateModel("Foo", [("name", models.CharField(max_length=255))]),
                migrations.CreateModel("Bar", [("size", models.IntegerField())]),
                migrations.DeleteModel("Bar"),
                migrations.DeleteModel("Foo"),
            ],
            [],
        )
        self.assertOptimizesTo(
            [
                migrations.CreateModel("Foo", [("name", models.CharField(max_length=255))]),
                migrations.CreateModel("Bar", [("size", models.IntegerField())]),
                migrations.DeleteModel("Foo"),
                migrations.DeleteModel("Bar"),
            ],
            [],
        )
        # This should not work - FK should block it
        self.assertOptimizesTo(
            [
                migrations.CreateModel("Foo", [("name", models.CharField(max_length=255))]),
                migrations.CreateModel("Bar", [("other", models.ForeignKey("testapp.Foo"))]),
                migrations.DeleteModel("Foo"),
            ],
            [
                migrations.CreateModel("Foo", [("name", models.CharField(max_length=255))]),
                migrations.CreateModel("Bar", [("other", models.ForeignKey("testapp.Foo"))]),
                migrations.DeleteModel("Foo"),
            ],
        )
        # This should not work - bases should block it
        self.assertOptimizesTo(
            [
                migrations.CreateModel("Foo", [("name", models.CharField(max_length=255))]),
                migrations.CreateModel("Bar", [("size", models.IntegerField())], bases=("testapp.Foo", )),
                migrations.DeleteModel("Foo"),
            ],
            [
                migrations.CreateModel("Foo", [("name", models.CharField(max_length=255))]),
                migrations.CreateModel("Bar", [("size", models.IntegerField())], bases=("testapp.Foo", )),
                migrations.DeleteModel("Foo"),
            ],
        )
