# -*- coding: utf-8 -*-
"""
@file    unittest_dijkstra.py
@author  Marek Heinrich
@date    2015-02-24
@version $Id: unittest_dijkstra.py 18096 2015-03-17 09:50:59Z behrisch $

Unittests for Dijkstra.

SUMO, Simulation of Urban MObility; see http://sumo.dlr.de/
Copyright (C) 2014-2015 DLR (http://www.dlr.de/) and contributors

This file is part of SUMO.
SUMO 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 3 of the License, or
(at your option) any later version.
"""

import sys
import unittest
import numpy
import math
import main


from commons import StarNode, DNList
from dijkstra_base import Dijkstra

from unittest_constants import VISUAL, lost, finished, afinished, unfinished, devel_skip, devel_run, broken, visual, known_bug, debugging


class someTestcaseDD(unittest.TestCase):

    # dijkstra

    @unittest.skipIf(finished, '')
    def test_dijkstra_nodes_id_creation(self):
        """test to check the id string and get_coords of a DijkstraNode"""

        myFlaeche = main.Flaeche(xdim=30, ydim=30, scale=1)
        myD = Dijkstra(myFlaeche,
                       start_node=(0, 0),
                       end_node=(20, 10))

        myDN_1 = StarNode(xx=3, yy=3, tt=4, dd=5, lastNode=None)

        self.assertEqual('3_3', myDN_1.id)
        self.assertEqual((3, 3), myDN_1.get_coords())

        # return myID = 21

    @unittest.skipIf(finished, 'done')
    def test_dijkstra_nodes_basic_funtionality(self):
        """testing init, get_min,  the home brewn node list """

        """test the ways of initialiation of DNLists"""
        myFlaeche = main.Flaeche(xdim=30, ydim=30, scale=1)
        myD = Dijkstra(myFlaeche, start_node=(0, 0), end_node=(20, 10))

        myDN_1 = StarNode(xx=3, yy=3, tt=4, dd=5, lastNode=None)
        myDN_2 = StarNode(xx=4, yy=3, tt=4, dd=5, lastNode=None)
        myDN_3 = StarNode(xx=5, yy=3, tt=4, dd=5, lastNode=None)
        myDN_4 = StarNode(xx=20, yy=9, tt=2, dd=1, lastNode=None)

        myDD_1 = DNList([myDN_1, myDN_2, myDN_3])
        myDD_2 = DNList([myDN_1, myDN_2, myDN_3])

        myDNL = DNList([myDN_1, myDN_2, myDN_3])
        self.assertEqual([ii for ii in myDNL], [myDN_1, myDN_2, myDN_3])

        """insert a few nodes into the list, and iter over them by id"""
        myDNL_id_1 = DNList([myDN_1, myDN_2, myDN_3], 'id')
        self.assertEqual([ii for ii in myDNL_id_1], ['3_3', '4_3', '5_3'])

        """alter the order of the few nodes , and iter over them by id"""
        myDNL_id_2 = DNList([myDN_3, myDN_2, myDN_1], 'id')
        self.assertEqual([ii for ii in myDNL_id_2], ['5_3', '4_3', '3_3'])

        """insert a few nodes into the list, and iter over them by tuple"""
        myDNL_tuple = DNList([myDN_1, myDN_2, myDN_3], 'tuple')
        self.assertEqual([ii for ii in myDNL_tuple], [(3, 3), (4, 3), (5, 3)])

        """return a node by its id, if not exists return none """
        myDNL_ret_id = DNList([myDN_1, myDN_2, myDN_3], 'id')
        self.assertEqual(myDNL_ret_id.get_by_id('3_3'), myDN_1)
        self.assertIsNone(myDNL_ret_id.get_by_id('5_5'))

        """return a node by its tuple, if not exists return none """
        myDNL_ret_tup = DNList([myDN_1, myDN_2, myDN_3], 'tuple')
        self.assertEqual(myDNL_ret_id.get_by_tuple((3, 3)), myDN_1)
        self.assertIsNone(myDNL_ret_id.get_by_tuple((5, 5)))

        """return a node by its tuple, even if the DNodeList iters on ids """
        myDNL_ret_tup = DNList([myDN_1, myDN_2, myDN_3], 'id')
        self.assertEqual(myDNL_ret_id.get_by_tuple((3, 3)), myDN_1)

        """test to retrive the minimal node from List"""
        myDNL_get_min = DNList([myDN_1, myDN_2, myDN_3, myDN_4])
        self.assertEqual(myDNL_get_min.get_min_node(), myDN_4)

        """test to retrive the minimal node and remove it from the list"""
        myDNL_pop_min = DNList([myDN_1, myDN_2, myDN_3, myDN_4])
        self.assertEqual(myDNL_pop_min.get_min_node(pop=True), myDN_4)
        self.assertNotEqual(myDNL_pop_min.get_min_node(), myDN_4)

        """test to get back the tuples of all nodes in the list"""
        myDNL_tup_list = DNList([myDN_1, myDN_2, myDN_3])
        self.assertEqual(myDNL_tup_list.get_tuples(), [(3, 3), (4, 3), (5, 3)])

        """test to see what happens if the node list is empty"""

        """make sure that only each node id is only once in the list
           should be already managed in the initalization
           keep a 'global'/class list with all nodes
           
           * handling alternation after creation - maybe function_closurures
           * overwriting the append function
           
           """

        """make sure that only nodes from the same Dijkstra are added"""
        # currently there is only one Dijkstra at a time

        """test to make sure, the list only takes dijkstra nodes"""
        # this does not work - not too urgent now
        # self.assertRaises(AssertionError, Dijkstra.DNList, [myDN_1, myDN_2, 1] )

        """insert a few nodes into the list, and iter over them returning 
           the distance traveled"""  # to be done when needed

        """insert a few nodes into the list, and iter over them returning 
           the total distance """   # to be done when needed

        # return myId 22

    @unittest.skipIf(finished, 'done')
    def test_dikstra_get_nodes_list(self):
        """test the getter of the nodes lists"""

        start = (10, 10)
        end = (20, 10)
        myFlaeche = main.Flaeche(xdim=30, ydim=30, scale=1)
        myD = Dijkstra(myFlaeche, start, end)
        self.assertEqual(myD.get_open_nodes(), [])
        myDN_1 = StarNode(xx=3, yy=3, tt=4, dd=5, lastNode=None)
        myDN_2 = StarNode(xx=4, yy=3, tt=4, dd=5, lastNode=None)
        myDN_3 = StarNode(xx=5, yy=5, tt=4, dd=5, lastNode=None)
        myDN_4 = StarNode(xx=5, yy=6, tt=4, dd=5, lastNode=None)

        """get the nodes tuples"""
        myD.open_nodes_list[0:0] = [myDN_1, myDN_2]
        self.assertEqual(
            sorted(myD.get_open_nodes('tuples')), sorted([(3, 3), (4, 3)]))

        """get the node them self """
        self.assertEqual(
            sorted(myD.get_open_nodes()), sorted([myDN_1, myDN_2]))

        """get the node tuples of closed list """
        myD.closed_nodes_list[0:0] = [myDN_3, myDN_4]
        self.assertEqual(
            sorted(myD.get_closed_nodes('tuples')), sorted([(5, 5), (5, 6)]))
        # return myId = 7

    @unittest.skipIf(finished, 'done')
    def test_get_neighbours(self):
        """test to receive any neigbour, regardless wheather legal or not"""

        start = (0,  0)
        end = (20, 10)
        myFlaeche = main.Flaeche(xdim=30, ydim=30, scale=1)
        myD = Dijkstra(myFlaeche, start, end)
        self.assertEqual(myFlaeche.get_neighbours((2, 2)), [(1, 1), (1, 2), (1, 3),
                                                            (2, 1), (2, 3),
                                                            (3, 1), (3, 2), (3, 3)])
        self.assertEqual(
            myFlaeche.get_neighbours((0, 2)), [(0, 1), (0, 3), (1, 1), (1, 2), (1, 3)])
        # return myId = 5

    @unittest.skipIf(finished, 'done')
    def test_neighbours(self):
        """test to vertify two cells are neibourghs """

        start = (0,  0)
        end = (20, 10)
        myFlaeche = main.Flaeche(xdim=30, ydim=30, scale=1)
        myD = Dijkstra(myFlaeche, start, end)
        self.assertTrue(myFlaeche.neighbours((2, 2), (1, 1)))
        self.assertTrue(myFlaeche.neighbours((2, 2), (2, 3)))
        self.assertFalse(myFlaeche.neighbours((2, 2), (2, 4)))
        self.assertRaises(AssertionError, myFlaeche.neighbours, (2, 2), (2, 2))

    @unittest.skipIf(finished, 'done')
    def test_dijkstra_to_do(self):
        """test for illegal start or end possitions"""

        start = (0, 0)
        end = (20, 10)
        myFlaeche = main.Flaeche(xdim=10, ydim=10, scale=1)
        #    self.assertRaises(StandardError, Dijkstra, myFlaeche, ( 1,  1), ( 1,  1) )

        self.assertRaises(
            StandardError, Dijkstra, myFlaeche, (1,  1), (11, 11))
        self.assertRaises(
            StandardError, Dijkstra, myFlaeche, (1,  1), (11,  1))
        self.assertRaises(StandardError, Dijkstra, myFlaeche, (1,  1), (1, 11))

        self.assertRaises(
            StandardError, Dijkstra, myFlaeche, (1, 11), (11, 11))
        self.assertRaises(
            StandardError, Dijkstra, myFlaeche, (1, 11), (11,  1))
        self.assertRaises(StandardError, Dijkstra, myFlaeche, (1, 11), (1,  1))
        self.assertRaises(StandardError, Dijkstra, myFlaeche, (1, 11), (1, 11))

        self.assertRaises(
            StandardError, Dijkstra, myFlaeche, (11,  1), (11, 11))
        self.assertRaises(
            StandardError, Dijkstra, myFlaeche, (11,  1), (11,  1))
        self.assertRaises(
            StandardError, Dijkstra, myFlaeche, (11,  1), (1,  1))
        self.assertRaises(
            StandardError, Dijkstra, myFlaeche, (11,  1), (1, 11))

        self.assertRaises(
            StandardError, Dijkstra, myFlaeche, (11, 11), (11, 11))
        self.assertRaises(
            StandardError, Dijkstra, myFlaeche, (11, 11), (11,  1))
        self.assertRaises(
            StandardError, Dijkstra, myFlaeche, (11, 11), (1,  1))
        self.assertRaises(
            StandardError, Dijkstra, myFlaeche, (11, 11), (1, 11))

        self.assertRaises(StandardError, Dijkstra, myFlaeche, (11, 11), (0, 0))
        # return myId = 9

    # is visual
    @unittest.skipIf(finished, 'done')
    def test_dijsktra_step_internals(self):
        """ Test for dijkstra's results after the first step        """

        """test if the start point is added to the open node list"""
        myFlaeche = main.Flaeche(xdim=300, ydim=300, scale=10)
        myD_step_zero = Dijkstra(myFlaeche, (0, 0), (20, 10))
        myD_step_zero.step()
        self.assertEqual(myD_step_zero.get_open_nodes('tuples'), [(0, 0)])
        self.assertEqual(myD_step_zero.get_closed_nodes('tuples'), [])

        """ test if nodes are added to the closed list """
        myFlaeche = main.Flaeche(xdim=300, ydim=300, scale=10)
        myD = Dijkstra(myFlaeche, (0, 0), (20, 10))
        myD.step()
        myD.step(visual=False)
        self.assertEqual(
            myD.get_open_nodes('tuples'), [(0, 1), (1, 0), (1, 1)])
        self.assertEqual(myD.get_closed_nodes('tuples'), [(0, 0)])

        """test that nodes in the closed nodes list are omitted"""
        myD_fake_closed = Dijkstra(myFlaeche, (0, 0), (20, 10))

        myDN_1 = StarNode(xx=0, yy=0, tt=4, dd=5, lastNode=None)
        myDN_2 = StarNode(xx=1, yy=1, tt=4, dd=5, lastNode=None)

        myD_fake_closed.open_nodes_list.append(myDN_1)
        myD_fake_closed.closed_nodes_list.append(myDN_2)
        # should find myDN_2 as suspicious node
        # and reject as it alrerady is in closed node list
        myD_fake_closed.step()
        self.assertEqual(
            myD_fake_closed.get_open_nodes('tuples'), [(0, 1), (1, 0)])

        """test that nodes in the open lists are updated"""
        myD_fake_open = Dijkstra(myFlaeche, (0, 0), (20, 10))

        myDN_0 = StarNode(xx=1, yy=1, tt=400, dd=500, lastNode=None)
        myDN_1 = StarNode(xx=0, yy=0, tt=4, dd=5, lastNode=None)

        myD_fake_open.open_nodes_list.append(myDN_0)
        myD_fake_open.open_nodes_list.append(myDN_1)
        # should now take myDN_1 as next node
        # and finding a shorter path to myDN_0
        # and should update the open list after the step
        self.assertEqual(
            sorted(myD_fake_open.get_open_nodes('tuples')), [(0, 0), (1, 1)])
        long_way = DNList(myD_fake_open.open_nodes_list).get_by_tuple(
            (1, 1)).full_costs
        """test the unfished return value"""
        self.assertFalse(myD_fake_open.step())
        short_way = DNList(myD_fake_open.open_nodes_list).get_by_tuple(
            (1, 1)).full_costs
        self.assertGreater(long_way, short_way)

        """test that the algo stops if the end is reached - manipulated lists"""
        myD_fake_end = Dijkstra(myFlaeche, (0, 0), (20, 10))

        myDN_1 = StarNode(xx=20, yy=10, tt=4, dd=0, lastNode=None)
        myDN_2 = StarNode(xx=12, yy=10, tt=4, dd=7, lastNode=None)

        # dest point in open and the shortest
        myD_fake_end.open_nodes_list.append(myDN_1)
        myD_fake_end.open_nodes_list.append(myDN_2)  # some point the longest
        self.assertTrue(myD_fake_end.step())

    @unittest.skipIf(finished, 'done')
    def test_dijsktra_run_and_rebuild(self):
        """run the algorithm on a simple example"""
        visual = VISUAL
        visual = True
        myFlaeche = main.Flaeche(
            xdim=300, ydim=300, scale=10, output='result_hindrance_punctual')
        myD = Dijkstra(myFlaeche, (0, 0), (10, 10))
        myD.run()
        myD.rebuild_path()
        path = DNList(myD.path, 'tuples').get_tuples()
        self.assertEqual(path, [(0, 0), (1, 1), (2, 2), (3, 3),
                                (4, 4), (5, 5), (6, 6), (7, 7),
                                (8, 8), (9, 9), (10, 10)])
        if visual:
            myD.draw_path()
        del(myD)

        myD = Dijkstra(myFlaeche, (0, 0), (10, 10))
        self.assertRaisesRegexp(
            StandardError, "algorithm must be run first successfully", myD.rebuild_path)

        del(myD)
        blocked_nodes = [(xx, 15) for xx in range(5, 25)]
        myFlaeche.load_node_data(blocked_nodes)
        myD = Dijkstra(myFlaeche, (3, 11), (16, 19))
        myD.run()
        myD.rebuild_path()
        self.assertEqual(DNList(myD.path, 'tuples').get_tuples(),
                         [(3, 11), (4, 12), (4, 13), (4, 14), (4, 15),
                          (5, 16), (6, 16), (7, 16), (8, 16), (9, 16),
                          (10, 16), (11, 17), (12, 17), (13, 18),
                          (14, 18), (15, 18), (16, 19)])

        if visual:
            myD.draw_path()

    @unittest.skipIf(devel_run, 'done')
    def test_wiki(self):
        visual = VISUAL
#        visual = True
        """run the aglo like in the wikipedia example"""
        myFlaeche = main.Flaeche(
            xdim=300, ydim=300, scale=10, output='result_mediawiki_example')
        myD = Dijkstra(myFlaeche, (17, 3), (3, 17))

        blocked_nodes = [(xx, 7) for xx in range(4, 16)]
        blocked_nodes[0:0] = [(xx, 8) for xx in range(4, 16)]
        blocked_nodes[0:0] = [(xx, 9) for xx in range(4, 16)]
        blocked_nodes[0:0] = [(xx, 10) for xx in range(13, 16)]
        blocked_nodes[0:0] = [(xx, 11) for xx in range(13, 16)]
        blocked_nodes[0:0] = [(xx, 12) for xx in range(13, 16)]
        blocked_nodes[0:0] = [(xx, 13) for xx in range(13, 16)]
        blocked_nodes[0:0] = [(xx, 14) for xx in range(13, 16)]

        myFlaeche.load_node_data(blocked_nodes)
        myD = Dijkstra(myFlaeche, (3, 19), (18, 3))
        myD.run(visual=visual)
        myD.rebuild_path()
        self.assertEqual(DNList(myD.path, 'tuples').get_tuples(),
                         [(3, 19), (4, 18), (5, 17), (6, 16), (7, 15), (8, 15),
                          (9, 15), (10, 15), (11, 15), (12,
                                                        15), (13, 15), (14, 15),
                          (15, 15), (16, 14), (16, 13), (16,
                                                         12), (16, 11), (16, 10),
                          (16, 9), (16, 8), (16, 7), (17, 6), (17, 5), (17, 4), (18, 3)])

        if visual:
            myD.draw_path(final=True)
            main.make_movie(myFlaeche.output)

    @unittest.skipIf(finished, 'done')
    def test_get_distance_to_end(self):
        """ttel the distance heurisitics from the point to dest."""

        myFlaeche = main.Flaeche(xdim=30, ydim=30, scale=1)
        myD = Dijkstra(myFlaeche, (10, 10), (20, 10))
        self.assertEqual(myD.get_distance_to_end((15, 10)), 5)
        #self.assertEqual(myD.get_distance_to_end((15, 15)), 1.41421 * 5)
        # reach the end
        self.assertEqual(myD.get_distance_to_end((20, 10)), 0)
        self.assertEqual(myD.get_distance_to_end((20, 20)), 10)  # negaive root
        self.assertRaises(
            StandardError, myD.get_distance_to_end, (40, 40))  # illegal poi

        # return myId = 6

    # very long - hence at the end
    @unittest.skipIf(finished, 'done')
    def test_get_distance_between(self):
        """test the correct disstance between two points"""

        myId = 12
        myFlaeche = main.Flaeche(xdim=30, ydim=30, scale=1)
        myD = Dijkstra(myFlaeche, (10, 10), (20, 10))
        # points are equal
        self.assertEqual(myD.get_distance_between_points((2, 2), (2, 2)), 0)
        # point a outside
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (-3, -3), (10, 10))
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (-3,  8), (10, 10))
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (8, -3), (10, 10))
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (8, 40), (10, 10))
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (40,  8), (10, 10))
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (40, 40), (10, 10))

        # point b outside
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (10, 10), (-3, -3))
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (10, 10), (-3,  8))
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (10, 10), (8, -3))
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (10, 10), (8, 40))
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (10, 10), (40,  8))
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (10, 10), (40, 40))

        # both points outside - check if both are different illegal values and check
        # that even if twice the same illegal point is submitted it will not be
        # accepted
        illegal_points = [
            (-3, -3), (-3, 8), (8, -3), (40, 40), (40, 8), (8, 40)]
        for ilpt_1 in illegal_points:
            for ilpt_2 in illegal_points:
                # print ilpt_1, ilpt_2
                self.assertRaises(
                    StandardError, myD.get_distance_between_points, ilpt_1, ilpt_2)

        # both points are next to each other / all around
        self.assertEqual(
            myD.get_distance_between_points((10, 10), (10,  9)), myFlaeche.scale)
        self.assertEqual(
            myD.get_distance_between_points((10, 10), (10, 11)), myFlaeche.scale)
        self.assertEqual(
            myD.get_distance_between_points((10, 10), (9, 10)), myFlaeche.scale)
        self.assertEqual(
            myD.get_distance_between_points((10, 10), (11, 10)), myFlaeche.scale)

        self.assertAlmostEqual(myD.get_distance_between_points(
            (10, 10), (9,  9)), myFlaeche.scale * 1.41421356)
        self.assertAlmostEqual(myD.get_distance_between_points(
            (10, 10), (9, 11)), myFlaeche.scale * 1.41421356)
        self.assertAlmostEqual(myD.get_distance_between_points(
            (10, 10), (11,  9)), myFlaeche.scale * 1.41421356)
        self.assertAlmostEqual(myD.get_distance_between_points(
            (10, 10), (11, 11)), myFlaeche.scale * 1.41421356)

        # 2 points a bit off
        self.assertAlmostEqual(
            myD.get_distance_between_points((10, 10), (20, 10)), 10)
        self.assertAlmostEqual(
            myD.get_distance_between_points((10, 10), (21, 15)), 12, 0)
        # 121 + 25 = 145  appox 12 * 12

        # same again with a driffent scale
        myFlaeche = main.Flaeche(xdim=9, ydim=9, scale=0.25)
        myD = Dijkstra(myFlaeche, (10, 10), (20, 10))
        # points are equal
        self.assertEqual(myD.get_distance_between_points((2, 2), (2, 2)), 0)
        # point a outside
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (-3, -3), (10, 10))
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (-3,  8), (10, 10))
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (8, -3), (10, 10))
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (8, 40), (10, 10))
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (40,  8), (10, 10))
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (40, 40), (10, 10))

        # point b outside
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (10, 10), (-3, -3))
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (10, 10), (-3,  8))
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (10, 10), (8, -3))
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (10, 10), (8, 40))
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (10, 10), (40,  8))
        self.assertRaises(
            StandardError, myD.get_distance_between_points, (10, 10), (40, 40))

        # both points outside - check if both are different illegal values and check
        # that even if twice the same illegal point is submitted it will not be
        # accepted
        illegal_points = [
            (-3, -3), (-3, 8), (8, -3), (40, 40), (40, 8), (8, 40)]
        for ilpt_1 in illegal_points:
            for ilpt_2 in illegal_points:
                # print ilpt_1, ilpt_2
                self.assertRaises(
                    StandardError, myD.get_distance_between_points, ilpt_1, ilpt_2)

        # both points are next to each other all around
        self.assertEqual(
            myD.get_distance_between_points((10, 10), (10,  9)), myFlaeche.scale)
        self.assertEqual(
            myD.get_distance_between_points((10, 10), (10, 11)), myFlaeche.scale)
        self.assertEqual(
            myD.get_distance_between_points((10, 10), (9, 10)), myFlaeche.scale)
        self.assertEqual(
            myD.get_distance_between_points((10, 10), (11, 10)), myFlaeche.scale)

        self.assertAlmostEqual(myD.get_distance_between_points(
            (10, 10), (9,  9)), myFlaeche.scale * 1.41421356)
        self.assertAlmostEqual(myD.get_distance_between_points(
            (10, 10), (9, 11)), myFlaeche.scale * 1.41421356)
        self.assertAlmostEqual(myD.get_distance_between_points(
            (10, 10), (11,  9)), myFlaeche.scale * 1.41421356)
        self.assertAlmostEqual(myD.get_distance_between_points(
            (10, 10), (11, 11)), myFlaeche.scale * 1.41421356)

        # 2 points a bit off
        self.assertAlmostEqual(
            myD.get_distance_between_points((10, 10), (20, 10)), 2.5)
        self.assertAlmostEqual(
            myD.get_distance_between_points((10, 10), (21, 15)), 3, 0)
        # 121 + 25 = 145  appox 12 * 12
