-- | From a list of group sizes build a function assigning every element a
-- unique color, nicely distributed such that they are well differentiated both
-- using color and monochrome displays.
genColorGroups :: RealFrac t =>
                  ColorParams t
               -> [Int]                -- ^ List of group sizes.
               -> [((Int,Int),(HSV t))]
genColorGroups (ColorParams {
              cpScale = scale
            , cpZeroHue = zeroHue
            , cpVBottom = vBot, cpVRange = vRan
            , cpSBottom = sBot, cpSRange = sRan
            }) groups =
  do
    (groupIdx, groupSize) <- zip [0.. ] groups
    elemIdx <- [0..groupSize - 1]
    let h = toShiftedGroupHue groupIdx (fromIntegral elemIdx / fromIntegral groupSize)
        v = vBot + vRan * toGroupHue groupIdx (fromIntegral elemIdx / fromIntegral groupSize)
        s = sBot + sRan * toGroupHue groupIdx (fromIntegral elemIdx / fromIntegral groupSize)
        color = HSV (360*h) s v
    return ((groupIdx, elemIdx), color)
  where
    nGroups :: Int
    nGroups = length groups

    toGroupHue g h = (
      fromIntegral g    + -- base position
      0.5 * (1 - scale) + -- left margin
            (h * scale)   -- position in margin
      ) / (fromIntegral nGroups)

    toShiftedGroupHue g h =
      snd . properFraction $ toGroupHue g h + 1 +
      (zeroHue/360) - toGroupHue 0 0.5


-- | A good default style for the 'genColorGroups' color palette function. The
-- parameter shifts the hue for the first group.
genColorGroups :: RealFrac t =>
                  ColorParams t
               -> [Int]                -- ^ List of group sizes.
               -> [((Int,Int),(HSV t))]
genColorGroups (ColorParams {
              cpScale = scale
            , cpZeroHue = zeroHue
            , cpVBottom = vBot, cpVRange = vRan
            , cpSBottom = sBot, cpSRange = sRan
            }) groups =
  do
    (groupIdx, groupSize) <- zip [0.. ] groups
    elemIdx <- [0..groupSize - 1]
    let h = toShiftedGroupHue groupIdx (fromIntegral elemIdx / fromIntegral groupSize)
        v = vBot + vRan * toGroupHue groupIdx (fromIntegral elemIdx / fromIntegral groupSize)
        s = sBot + sRan * toGroupHue groupIdx (fromIntegral elemIdx / fromIntegral groupSize)
        color = HSV (360*h) s v
    return ((groupIdx, elemIdx), color)
  where
    nGroups :: Int
    nGroups = length groups

    toGroupHue g h = (
      fromIntegral g    + -- base position
      0.5 * (1 - scale) + -- left margin
            (h * scale)   -- position in margin
      ) / (fromIntegral nGroups)

    toShiftedGroupHue g h =
      snd . properFraction $ toGroupHue g h + 1 +
      (zeroHue/360) - toGroupHue 0 0.5


-- | A good default style for the 'genColorGroups' color palette function. The
-- parameter shifts the hue for the first group.
genColorGroups (ColorParams {
              cpScale = scale
            , cpZeroHue = zeroHue
            , cpVBottom = vBot, cpVRange = vRan
            , cpSBottom = sBot, cpSRange = sRan
            }) groups =
  do
    (groupIdx, groupSize) <- zip [0.. ] groups
    elemIdx <- [0..groupSize - 1]
    let h = toShiftedGroupHue groupIdx (fromIntegral elemIdx / fromIntegral groupSize)
        v = vBot + vRan * toGroupHue groupIdx (fromIntegral elemIdx / fromIntegral groupSize)
        s = sBot + sRan * toGroupHue groupIdx (fromIntegral elemIdx / fromIntegral groupSize)
        color = HSV (360*h) s v
    return ((groupIdx, elemIdx), color)
  where
    nGroups :: Int
    nGroups = length groups

    toGroupHue g h = (
      fromIntegral g    + -- base position
      0.5 * (1 - scale) + -- left margin
            (h * scale)   -- position in margin
      ) / (fromIntegral nGroups)

    toShiftedGroupHue g h =
      snd . properFraction $ toGroupHue g h + 1 +
      (zeroHue/360) - toGroupHue 0 0.5


-- | A good default style for the 'genColorGroups' color palette function. The
-- parameter shifts the hue for the first group.