runGroupBy ::
       RGroupPredicate   -- ^ Grouping predicate, in order to form the groups of RTuples (it defines when two RTuples should be included in the same group)
    -> [RAggOperation]   -- ^ Aggregations to be applied on specific columns
    -> [ColumnName]      -- ^ List of grouping column names (GROUP BY clause in SQL)
                         --   We assume that all RTuples in the same group have the same value in these columns
    -> RTable            -- ^ input RTable
    -> RTable            -- ^ output RTable
runGroupBy gpred aggOps cols rtab =  
    if isRTabEmpty rtab
        then emptyRTable
        else 
            let -- rtupList = V.toList rtab
                
                -- 1. form the groups of RTuples
                    -- a. first sort the Rtuples based on the grouping columns
                    -- This is a very important step if we want the groupBy operation to work. This is because grouping on lists is
                    -- implemented like this: group "Mississippi" = ["M","i","ss","i","ss","i","pp","i"]
                    -- So we have to sort the list first in order to get the right grouping:
                    -- group (sort "Mississippi") = ["M","iiii","pp","ssss"]
                
                listOfRTupSorted = rtableToList $ runOrderBy (createOrderingSpec cols) rtab

                -- debug
               -- !dummy1 = trace (show listOfRTupSorted) True

                   -- Data.List.sortBy (\t1 t2 -> if (gpred t1 t2) then EQ else compare (rtupleToList t1) (rtupleToList t2) ) rtupList --(t1!(Data.List.head . HM.keys $ t1)) (t2!(Data.List.head . HM.keys $ t2)) ) rtupList
                                   
                                   -- Data.List.map (HM.fromList) $ Data.List.sort $ Data.List.map (HM.toList) rtupList 
                                    --(t1!(Data.List.head . HM.keys $ t1)) (t2!(Data.List.head . HM.keys $ t2)) ) rtupList
                    
                    -- b then produce the groups
                listOfRTupGroupLists = Data.List.groupBy gpred listOfRTupSorted       

                -- debug
              --  !dummy2 = trace (show listOfRTupGroupLists) True

                -- 2. turn each (sub)list of Rtuples representing a Group into an RTable in order to apply aggregation
                --    Note: each RTable in this list will hold a group of RTuples that all have the same values in the input grouping columns
                --    (which must be compatible with the input grouping predicate)
                listofGroupRtabs = Data.List.map (rtableFromList) listOfRTupGroupLists
                -- 3. We need to keep the values of the grouping columns (e.g., by selecting the fisrt row) from each one of these RTables,
                --    in order to "join them" with the aggregated RTuples that will be produced by the aggregation operations
                --    The following will produce a list of singleton RTables.
                listOfGroupingColumnsRtabs = Data.List.map ( (restrictNrows 1) . (p cols) ) listofGroupRtabs

                -- debug
              --  !dummy3 = trace (show listOfGroupingColumnsRtabs) True

                -- 4. Aggregate each group according to input and produce a list of (aggregated singleton RTables)
                listOfAggregatedRtabs = Data.List.map (rAgg aggOps) listofGroupRtabs

                -- debug
              --  !dummy4 = trace (show listOfAggregatedRtabs ) True

                -- 5. Join the two list of singleton RTables
                listOfFinalRtabs = Data.List.zipWith (iJ (\t1 t2 -> True)) listOfGroupingColumnsRtabs listOfAggregatedRtabs

                -- debug
              --  !dummy5 = trace (show listOfFinalRtabs) True


                -- 6. Union all individual singleton RTables into the final RTable
            in  Data.List.foldr1 (u) listOfFinalRtabs
            where
                createOrderingSpec :: [ColumnName] -> [(ColumnName, OrderingSpec)]
                createOrderingSpec cols = Data.List.zip cols (Data.List.take (Data.List.length cols) $ Data.List.repeat Asc)
runGroupBy ::
       RGroupPredicate   -- ^ Grouping predicate, in order to form the groups of RTuples (it defines when two RTuples should be included in the same group)
    -> [RAggOperation]   -- ^ Aggregations to be applied on specific columns
    -> [ColumnName]      -- ^ List of grouping column names (GROUP BY clause in SQL)
                         --   We assume that all RTuples in the same group have the same value in these columns
    -> RTable            -- ^ input RTable
    -> RTable
runGroupBy gpred aggOps cols rtab =  
    if isRTabEmpty rtab
        then emptyRTable
        else 
            let -- rtupList = V.toList rtab
                
                -- 1. form the groups of RTuples
                    -- a. first sort the Rtuples based on the grouping columns
                    -- This is a very important step if we want the groupBy operation to work. This is because grouping on lists is
                    -- implemented like this: group "Mississippi" = ["M","i","ss","i","ss","i","pp","i"]
                    -- So we have to sort the list first in order to get the right grouping:
                    -- group (sort "Mississippi") = ["M","iiii","pp","ssss"]
                
                listOfRTupSorted = rtableToList $ runOrderBy (createOrderingSpec cols) rtab

                -- debug
               -- !dummy1 = trace (show listOfRTupSorted) True

                   -- Data.List.sortBy (\t1 t2 -> if (gpred t1 t2) then EQ else compare (rtupleToList t1) (rtupleToList t2) ) rtupList --(t1!(Data.List.head . HM.keys $ t1)) (t2!(Data.List.head . HM.keys $ t2)) ) rtupList
                                   
                                   -- Data.List.map (HM.fromList) $ Data.List.sort $ Data.List.map (HM.toList) rtupList 
                                    --(t1!(Data.List.head . HM.keys $ t1)) (t2!(Data.List.head . HM.keys $ t2)) ) rtupList
                    
                    -- b then produce the groups
                listOfRTupGroupLists = Data.List.groupBy gpred listOfRTupSorted       

                -- debug
              --  !dummy2 = trace (show listOfRTupGroupLists) True

                -- 2. turn each (sub)list of Rtuples representing a Group into an RTable in order to apply aggregation
                --    Note: each RTable in this list will hold a group of RTuples that all have the same values in the input grouping columns
                --    (which must be compatible with the input grouping predicate)
                listofGroupRtabs = Data.List.map (rtableFromList) listOfRTupGroupLists
                -- 3. We need to keep the values of the grouping columns (e.g., by selecting the fisrt row) from each one of these RTables,
                --    in order to "join them" with the aggregated RTuples that will be produced by the aggregation operations
                --    The following will produce a list of singleton RTables.
                listOfGroupingColumnsRtabs = Data.List.map ( (restrictNrows 1) . (p cols) ) listofGroupRtabs

                -- debug
              --  !dummy3 = trace (show listOfGroupingColumnsRtabs) True

                -- 4. Aggregate each group according to input and produce a list of (aggregated singleton RTables)
                listOfAggregatedRtabs = Data.List.map (rAgg aggOps) listofGroupRtabs

                -- debug
              --  !dummy4 = trace (show listOfAggregatedRtabs ) True

                -- 5. Join the two list of singleton RTables
                listOfFinalRtabs = Data.List.zipWith (iJ (\t1 t2 -> True)) listOfGroupingColumnsRtabs listOfAggregatedRtabs

                -- debug
              --  !dummy5 = trace (show listOfFinalRtabs) True


                -- 6. Union all individual singleton RTables into the final RTable
            in  Data.List.foldr1 (u) listOfFinalRtabs
            where
                createOrderingSpec :: [ColumnName] -> [(ColumnName, OrderingSpec)]
                createOrderingSpec cols = Data.List.zip cols (Data.List.take (Data.List.length cols) $ Data.List.repeat Asc)
runGroupBy gpred aggOps cols rtab =  
    if isRTabEmpty rtab
        then emptyRTable
        else 
            let -- rtupList = V.toList rtab
                
                -- 1. form the groups of RTuples
                    -- a. first sort the Rtuples based on the grouping columns
                    -- This is a very important step if we want the groupBy operation to work. This is because grouping on lists is
                    -- implemented like this: group "Mississippi" = ["M","i","ss","i","ss","i","pp","i"]
                    -- So we have to sort the list first in order to get the right grouping:
                    -- group (sort "Mississippi") = ["M","iiii","pp","ssss"]
                
                listOfRTupSorted = rtableToList $ runOrderBy (createOrderingSpec cols) rtab

                -- debug
               -- !dummy1 = trace (show listOfRTupSorted) True

                   -- Data.List.sortBy (\t1 t2 -> if (gpred t1 t2) then EQ else compare (rtupleToList t1) (rtupleToList t2) ) rtupList --(t1!(Data.List.head . HM.keys $ t1)) (t2!(Data.List.head . HM.keys $ t2)) ) rtupList
                                   
                                   -- Data.List.map (HM.fromList) $ Data.List.sort $ Data.List.map (HM.toList) rtupList 
                                    --(t1!(Data.List.head . HM.keys $ t1)) (t2!(Data.List.head . HM.keys $ t2)) ) rtupList
                    
                    -- b then produce the groups
                listOfRTupGroupLists = Data.List.groupBy gpred listOfRTupSorted       

                -- debug
              --  !dummy2 = trace (show listOfRTupGroupLists) True

                -- 2. turn each (sub)list of Rtuples representing a Group into an RTable in order to apply aggregation
                --    Note: each RTable in this list will hold a group of RTuples that all have the same values in the input grouping columns
                --    (which must be compatible with the input grouping predicate)
                listofGroupRtabs = Data.List.map (rtableFromList) listOfRTupGroupLists
                -- 3. We need to keep the values of the grouping columns (e.g., by selecting the fisrt row) from each one of these RTables,
                --    in order to "join them" with the aggregated RTuples that will be produced by the aggregation operations
                --    The following will produce a list of singleton RTables.
                listOfGroupingColumnsRtabs = Data.List.map ( (restrictNrows 1) . (p cols) ) listofGroupRtabs

                -- debug
              --  !dummy3 = trace (show listOfGroupingColumnsRtabs) True

                -- 4. Aggregate each group according to input and produce a list of (aggregated singleton RTables)
                listOfAggregatedRtabs = Data.List.map (rAgg aggOps) listofGroupRtabs

                -- debug
              --  !dummy4 = trace (show listOfAggregatedRtabs ) True

                -- 5. Join the two list of singleton RTables
                listOfFinalRtabs = Data.List.zipWith (iJ (\t1 t2 -> True)) listOfGroupingColumnsRtabs listOfAggregatedRtabs

                -- debug
              --  !dummy5 = trace (show listOfFinalRtabs) True


                -- 6. Union all individual singleton RTables into the final RTable
            in  Data.List.foldr1 (u) listOfFinalRtabs
            where
                createOrderingSpec :: [ColumnName] -> [(ColumnName, OrderingSpec)]
                createOrderingSpec cols = Data.List.zip cols (Data.List.take (Data.List.length cols) $ Data.List.repeat Asc)