-- | Implements a Left Outer Join operation between two RTables (any type of join predicate is allowed),
-- i.e., the rows of the left RTable will be preserved.
-- Note that when dublicate keys encountered that is, since the underlying structure for an RTuple is a Data.HashMap.Strict,
-- only one value per key is allowed. So in the context of joining two RTuples the value of the left RTuple on the common key will be prefered.
{-runLeftJoin ::
    RJoinPredicate
    -> RTable
    -> RTable
    -> RTable
runLeftJoin jpred leftRTab rtab = do
    rtupLeft <- leftRTab
    rtup <- rtab
    let targetRtuple = 
            if (jpred rtupLeft rtup)
            then HM.union rtupLeft rtup
            else HM.union rtupLeft (createNullRTuple colNamesList)
                    where colNamesList = HM.keys rtup
    --return targetRtuple

    -- remove repeated rtuples and keep just the rtuples of the preserving rtable
    iJ (jpred2) (return targetRtuple) leftRTab
        where 
            -- the left tuple is the extended (result of the outer join)
            -- the right tuple is from the preserving table
            -- we need to return True for those who are equal in the common set of columns
            jpred2 tL tR = 
                let left = HM.toList tL
                    right = HM.toList tR
                    -- in order to satisfy the join pred, all elements of right must exist in left
                in  Data.List.all (\(k,v) -> Data.List.elem (k,v) left) right
-}

-- | Implements a Left Outer Join operation between two RTables (any type of join predicate is allowed),
-- i.e., the rows of the left RTable will be preserved.
-- A Left Join :
-- @ 
-- tabLeft LEFT JOIN tabRight ON joinPred
-- @ 
-- where tabLeft is the preserving table can be defined as:
-- the Union between the following two RTables:
--  A. The result of the inner join: tabLeft INNER JOIN tabRight ON joinPred
--  B. The rows from the preserving table (tabLeft) that DONT satisfy the join condition, enhanced with the columns
--     of tabRight returning Null values.
--  The common columns will appear from both tables but only the left table column's will retain their original name. 
runLeftJoin ::
    RJoinPredicate
    -> RTable
    -> RTable
    -> RTable
runLeftJoin jpred preservingTab tab = 
    if isRTabEmpty preservingTab
        then 
            emptyRTable
        else
            if isRTabEmpty tab
                then
                    -- return the preserved tab 
                    preservingTab

                else 
                    -- we know that both the preservingTab and tab are non empty 
                    let 
                        unionFstPart = iJ jpred preservingTab tab 
                        -- project only the preserving tab's columns
                        fstPartProj = p (getColumnNamesfromRTab preservingTab) unionFstPart

                        -- the second part are the rows from the preserving table that dont join
                        -- we will use the Difference operations for this
                        unionSndPart = 
                            let 
                                difftab = d preservingTab fstPartProj -- unionFstPart
                                -- now enhance the result with the columns of the right table
                            in iJ (\t1 t2 -> True) difftab (createSingletonRTable $ createNullRTuple $ (getColumnNamesfromRTab tab))
                    in u unionFstPart unionSndPart
runLeftJoin ::
    RJoinPredicate
    -> RTable
    -> RTable
    -> RTable
runLeftJoin jpred preservingTab tab = 
    if isRTabEmpty preservingTab
        then 
            emptyRTable
        else
            if isRTabEmpty tab
                then
                    -- return the preserved tab 
                    preservingTab

                else 
                    -- we know that both the preservingTab and tab are non empty 
                    let 
                        unionFstPart = iJ jpred preservingTab tab 
                        -- project only the preserving tab's columns
                        fstPartProj = p (getColumnNamesfromRTab preservingTab) unionFstPart

                        -- the second part are the rows from the preserving table that dont join
                        -- we will use the Difference operations for this
                        unionSndPart = 
                            let 
                                difftab = d preservingTab fstPartProj -- unionFstPart
                                -- now enhance the result with the columns of the right table
                            in iJ (\t1 t2 -> True) difftab (createSingletonRTable $ createNullRTuple $ (getColumnNamesfromRTab tab))
                    in u unionFstPart unionSndPart
runLeftJoin jpred preservingTab tab = 
    if isRTabEmpty preservingTab
        then 
            emptyRTable
        else
            if isRTabEmpty tab
                then
                    -- return the preserved tab 
                    preservingTab

                else 
                    -- we know that both the preservingTab and tab are non empty 
                    let 
                        unionFstPart = iJ jpred preservingTab tab 
                        -- project only the preserving tab's columns
                        fstPartProj = p (getColumnNamesfromRTab preservingTab) unionFstPart

                        -- the second part are the rows from the preserving table that dont join
                        -- we will use the Difference operations for this
                        unionSndPart = 
                            let 
                                difftab = d preservingTab fstPartProj -- unionFstPart
                                -- now enhance the result with the columns of the right table
                            in iJ (\t1 t2 -> True) difftab (createSingletonRTable $ createNullRTuple $ (getColumnNamesfromRTab tab))
                    in u unionFstPart unionSndPart