def test_concatenate(self):
        """Test string concatenation."""
        # Define grammar rules
        excel_reference_prefix = "="
        excel_prefix = "CONCATENATE("
        excel_separator = ","
        excel_suffix = ")"
        python_reference_prefix = ""
        python_numpy_separator = "+"
        # A) Anchored
        a_res_cell = self.a_operand_1.concatenate(self.a_operand_2)
        # Compare words
        a_res_parsed = a_res_cell.parse
        self.assertEqual((excel_reference_prefix + excel_prefix +
                          self.coord_operand_1_excel + excel_separator +
                          self.coord_operand_2_excel + excel_suffix),
                         a_res_parsed['excel'])
        self.assertEqual(
            (python_reference_prefix + self.coord_operand_1_python +
             python_numpy_separator + self.coord_operand_2_python),
            a_res_parsed['python_numpy'])
        # Compare results of anchored
        self.assertAlmostEqual(
            a_res_cell.value,
            str(self.a_operand_1.value) + str(self.a_operand_2.value))

        # B) Un-anchored, numerical
        u_res_cell = self.u_operand_1.concatenate(self.u_operand_2)
        # Compare words
        u_res_parsed = u_res_cell.parse
        self.assertEqual(
            u_res_parsed['excel'],
            (excel_reference_prefix + excel_prefix + self.value_u_1 +
             excel_separator + self.value_u_2 + excel_suffix))
        self.assertEqual(
            u_res_parsed['python_numpy'],
            ('"' + python_reference_prefix + self.value_u_1 + '"' +
             python_numpy_separator + '"' + self.value_u_2 + '"'))
        # Compare results of un-anchored
        self.assertAlmostEqual(
            u_res_cell.value,
            str(self.u_operand_1.value) + str(self.u_operand_2.value))

        # C) Un-anchored, strings
        str_value = "6ppW1lPT"
        u_operand_1 = Cell(value=str_value, cell_indices=self.cell_indices)
        u_res_cell = u_operand_1.concatenate(self.u_operand_2)
        # Compare words
        u_res_parsed = u_res_cell.parse
        self.assertEqual(
            u_res_parsed['excel'],
            (excel_reference_prefix + excel_prefix + '"' + str_value + '"' +
             excel_separator + self.value_u_2 + excel_suffix))
        self.assertEqual(u_res_parsed['python_numpy'],
                         (python_reference_prefix + str_value +
                          python_numpy_separator + '"' + self.value_u_2 + '"'))
        # Compare results of un-anchored
        self.assertAlmostEqual(u_res_cell.value,
                               u_operand_1.value + str(self.u_operand_2.value))
class TestCellBinaryOperations(unittest.TestCase):
    """Test binary operations.

    Notice:
        if variable start with prefix 'a_' it means that cell is anchored
        (located in grid), if with prefix 'u_' it means that it is NOT
        anchored, with prefix 't' it does not matter (just a testing variable).
    """
    def setUp(self) -> None:
        self.cell_indices: CellIndices = CellIndices(5, 7)
        self.a_operand_1 = Cell(3, 4, 7, cell_indices=self.cell_indices)
        self.a_operand_2 = Cell(2, 4, 4, cell_indices=self.cell_indices)
        self.coord_operand_1_python = "values[3,4]"
        self.coord_operand_2_python = "values[2,4]"
        self.coord_operand_1_excel = "F5"
        self.coord_operand_2_excel = "F4"

        self.u_operand_1 = Cell(value=7, cell_indices=self.cell_indices)
        self.u_operand_2 = Cell(value=8, cell_indices=self.cell_indices)
        self.value_u_1 = "7"
        self.value_u_2 = "8"

    def _check_binary_operation(
        self,
        operation_method: Callable[[Cell, Cell], Cell],
        real_operation_fn: Callable[[float, float], float],
        excel_separator: str,
        python_numpy_separator: str,
        *,
        excel_prefix: str = "",
        excel_suffix: str = "",
        excel_reference_prefix: str = "=",
        python_reference_prefix: str = "",
    ) -> None:
        """Run the binary operation, compare numeric result, compare words.

        Args:
            operation_method (Callable[[Cell, Cell], Cell]): Pointer to the
                method inside the Cell class.
            real_operation_fn (Callable[[float, float], float]): Pointer to the
                Python method that compute the same.
            excel_separator (str): Separator of the operation in
                Excel language.
            python_numpy_separator (str): Separator of the operation in
                Python_NumPY language.
            excel_prefix (str): Prefix for the operation in Excel language.
            excel_suffix (str): Suffix for the operation in Excel language.
            excel_reference_prefix (str): Prefix of the word that reference
                to some computation in Excel.
            python_reference_prefix (str): Prefix of the word that reference
                to some computation in Python.
        """
        # A) Anchored
        a_res_cell = operation_method(self.a_operand_1, self.a_operand_2)
        # Compare words
        a_res_parsed = a_res_cell.parse
        self.assertEqual(a_res_parsed['excel'],
                         (excel_reference_prefix + excel_prefix +
                          self.coord_operand_1_excel + excel_separator +
                          self.coord_operand_2_excel + excel_suffix))
        self.assertEqual(
            a_res_parsed['python_numpy'],
            (python_reference_prefix + self.coord_operand_1_python +
             python_numpy_separator + self.coord_operand_2_python))
        # Compare results of anchored
        self.assertAlmostEqual(
            a_res_cell.value,
            real_operation_fn(self.a_operand_1.value, self.a_operand_2.value))
        # B) Un-anchored
        u_res_cell = operation_method(self.u_operand_1, self.u_operand_2)
        # Compare words
        u_res_parsed = u_res_cell.parse
        self.assertEqual(
            u_res_parsed['excel'],
            (excel_reference_prefix + excel_prefix + self.value_u_1 +
             excel_separator + self.value_u_2 + excel_suffix))
        self.assertEqual(u_res_parsed['python_numpy'],
                         (python_reference_prefix + self.value_u_1 +
                          python_numpy_separator + self.value_u_2))
        # Compare results of un-anchored
        self.assertAlmostEqual(
            u_res_cell.value,
            real_operation_fn(self.u_operand_1.value, self.u_operand_2.value))

    def test_string_equal(self):
        """Test if the strings are interpreted correctly in operations"""
        u_operand_str = Cell(None, None, "abc", cell_indices=self.cell_indices)
        comp = (self.a_operand_1 == u_operand_str)
        self.assertEqual(comp.parse['python_numpy'], 'values[3,4]=="abc"')
        self.assertEqual(comp.parse['excel'], '=F5="abc"')

    def test_add(self):
        """Test adding"""
        # Method test
        self._check_binary_operation(Cell.add, lambda x, y: x + y, "+", "+")
        # Operator test
        self._check_binary_operation(Cell.__add__, lambda x, y: x + y, "+",
                                     "+")

    def test_subtract(self):
        """Test subtracting"""
        # Method test
        self._check_binary_operation(Cell.subtract, lambda x, y: x - y, "-",
                                     "-")
        # Operator test
        self._check_binary_operation(Cell.__sub__, lambda x, y: x - y, "-",
                                     "-")

    def test_multiply(self):
        """Test multiplying"""
        # Method test
        self._check_binary_operation(Cell.multiply, lambda x, y: x * y, "*",
                                     "*")
        # Operator test
        self._check_binary_operation(Cell.__mul__, lambda x, y: x * y, "*",
                                     "*")

    def test_divide(self):
        """Test dividing"""
        # Method test
        self._check_binary_operation(Cell.divide, lambda x, y: x / y, "/", "/")
        # Operator test
        self._check_binary_operation(Cell.__truediv__, lambda x, y: x / y, "/",
                                     "/")

    def test_modulo(self):
        """Test modulo"""
        # Method test
        self._check_binary_operation(Cell.modulo,
                                     lambda x, y: x % y,
                                     ",",
                                     "%",
                                     excel_prefix="MOD(",
                                     excel_suffix=")")
        # Operator test
        self._check_binary_operation(Cell.__mod__,
                                     lambda x, y: x % y,
                                     ",",
                                     "%",
                                     excel_prefix="MOD(",
                                     excel_suffix=")")

    def test_power(self):
        """Test power"""
        # Method test
        self._check_binary_operation(Cell.power, lambda x, y: x**y, "^", "**")
        # Operator test
        self._check_binary_operation(Cell.__pow__, lambda x, y: x**y, "^",
                                     "**")

    def test_equalTo(self):
        """Test equal to"""
        # Method test
        self._check_binary_operation(Cell.equalTo, lambda x, y: x == y, "=",
                                     "==")
        # Operator test
        self._check_binary_operation(Cell.__eq__, lambda x, y: x == y, "=",
                                     "==")

    def test_notEqualTo(self):
        """Test not equal to"""
        # Method test
        self._check_binary_operation(Cell.notEqualTo, lambda x, y: x != y,
                                     "<>", "!=")
        # Operator test
        self._check_binary_operation(Cell.__ne__, lambda x, y: x != y, "<>",
                                     "!=")

    def test_greaterThan(self):
        """Test greater than"""
        # Method test
        self._check_binary_operation(Cell.greaterThan, lambda x, y: x > y, ">",
                                     ">")
        # Operator test
        self._check_binary_operation(Cell.__gt__, lambda x, y: x > y, ">", ">")

    def test_greaterThanOrEqualTo(self):
        """Test greater than or equal to"""
        # Method test
        self._check_binary_operation(Cell.greaterThanOrEqualTo,
                                     lambda x, y: x >= y, ">=", ">=")
        # Operator test
        self._check_binary_operation(Cell.__ge__, lambda x, y: x >= y, ">=",
                                     ">=")

    def test_lessThan(self):
        """Test less than"""
        # Method test
        self._check_binary_operation(Cell.lessThan, lambda x, y: x < y, "<",
                                     "<")
        # Operator test
        self._check_binary_operation(Cell.__lt__, lambda x, y: x < y, "<", "<")

    def test_lessThanOrEqualTo(self):
        """Test less than or equal to"""
        # Method test
        self._check_binary_operation(Cell.lessThanOrEqualTo,
                                     lambda x, y: x <= y, "<=", "<=")
        # Operator test
        self._check_binary_operation(Cell.__le__, lambda x, y: x <= y, "<=",
                                     "<=")

    def test_LogicalConjunction(self):
        """Test logical conjunction"""
        # Method test
        self._check_binary_operation(Cell.logicalConjunction,
                                     lambda x, y: x and y,
                                     ", ",
                                     " and ",
                                     excel_prefix="AND(",
                                     excel_suffix=")")
        # Operator test
        self._check_binary_operation(Cell.__and__,
                                     lambda x, y: x and y,
                                     ", ",
                                     " and ",
                                     excel_prefix="AND(",
                                     excel_suffix=")")

    def test_LogicalDisjunction(self):
        """Test logical disjunction"""
        # Method test
        self._check_binary_operation(Cell.logicalDisjunction,
                                     lambda x, y: x or y,
                                     ", ",
                                     " or ",
                                     excel_prefix="OR(",
                                     excel_suffix=")")
        # Operator test
        self._check_binary_operation(Cell.__or__,
                                     lambda x, y: x or y,
                                     ", ",
                                     " or ",
                                     excel_prefix="OR(",
                                     excel_suffix=")")

    def test_chain_of_operation(self):
        """Test multiple operations in a sequence"""
        result = self.a_operand_1 + self.a_operand_2 * self.u_operand_1
        self.assertDictEqual(
            {
                'python_numpy': 'values[3,4]+values[2,4]*7',
                'excel': '=F5+F4*7'
            }, result.parse)
        self.assertAlmostEqual(result.value, 35)

    def test_raw_statement(self):
        """Test raw statement"""
        result = Cell.raw(self.a_operand_1, {
            'python_numpy': "Hello from Python",
            'excel': "Excel welcomes"
        })
        self.assertDictEqual(
            {
                'python_numpy': "Hello from Python",
                'excel': "=Excel welcomes"  # Always computational
            },
            result.parse)
        self.assertAlmostEqual(self.a_operand_1.value, 7)

    def test_linear_interpolation(self):
        """Test the linear interpolation"""
        self.a_operand_1._value = 1
        self.a_operand_2._value = 3
        self.u_operand_1._value = 9
        res = Cell.linear_interpolation(self.a_operand_1, self.a_operand_2,
                                        self.a_operand_2, self.u_operand_1,
                                        self.u_operand_1)
        self.assertAlmostEqual(res.value, 27)
        self.assertDictEqual(
            {
                'python_numpy': 'np.interp(9, [values[3,4], values[2,4]], '
                '[values[2,4], 9])',
                'excel': '=(9-F4)*((9-F4)/(F4-F5))+9'
            }, res.parse)

    def test_concatenate(self):
        """Test string concatenation."""
        # Define grammar rules
        excel_reference_prefix = "="
        excel_prefix = "CONCATENATE("
        excel_separator = ","
        excel_suffix = ")"
        python_reference_prefix = ""
        python_numpy_separator = "+"
        # A) Anchored
        a_res_cell = self.a_operand_1.concatenate(self.a_operand_2)
        # Compare words
        a_res_parsed = a_res_cell.parse
        self.assertEqual((excel_reference_prefix + excel_prefix +
                          self.coord_operand_1_excel + excel_separator +
                          self.coord_operand_2_excel + excel_suffix),
                         a_res_parsed['excel'])
        self.assertEqual(
            (python_reference_prefix + self.coord_operand_1_python +
             python_numpy_separator + self.coord_operand_2_python),
            a_res_parsed['python_numpy'])
        # Compare results of anchored
        self.assertAlmostEqual(
            a_res_cell.value,
            str(self.a_operand_1.value) + str(self.a_operand_2.value))

        # B) Un-anchored, numerical
        u_res_cell = self.u_operand_1.concatenate(self.u_operand_2)
        # Compare words
        u_res_parsed = u_res_cell.parse
        self.assertEqual(
            u_res_parsed['excel'],
            (excel_reference_prefix + excel_prefix + self.value_u_1 +
             excel_separator + self.value_u_2 + excel_suffix))
        self.assertEqual(
            u_res_parsed['python_numpy'],
            ('"' + python_reference_prefix + self.value_u_1 + '"' +
             python_numpy_separator + '"' + self.value_u_2 + '"'))
        # Compare results of un-anchored
        self.assertAlmostEqual(
            u_res_cell.value,
            str(self.u_operand_1.value) + str(self.u_operand_2.value))

        # C) Un-anchored, strings
        str_value = "6ppW1lPT"
        u_operand_1 = Cell(value=str_value, cell_indices=self.cell_indices)
        u_res_cell = u_operand_1.concatenate(self.u_operand_2)
        # Compare words
        u_res_parsed = u_res_cell.parse
        self.assertEqual(
            u_res_parsed['excel'],
            (excel_reference_prefix + excel_prefix + '"' + str_value + '"' +
             excel_separator + self.value_u_2 + excel_suffix))
        self.assertEqual(u_res_parsed['python_numpy'],
                         (python_reference_prefix + str_value +
                          python_numpy_separator + '"' + self.value_u_2 + '"'))
        # Compare results of un-anchored
        self.assertAlmostEqual(u_res_cell.value,
                               u_operand_1.value + str(self.u_operand_2.value))