import Data.List (group, sort)
import Data.List.Split (chunksOf)
import Data.Numbers.Primes (primeFactors)



isZumkeller :: Int -> Bool
isZumkeller n =
  let ds = divisors n
      m = sum ds
   in ( even m
          && let half = div m 2
              in elem half ds
                   || ( all (half >=) ds
                          && summable half ds
                      )
      )

summable :: Int -> [Int] -> Bool
summable _ [] = False
summable x xs@(h: t) =
  elem x xs
    || summable (x - h) t
    || summable x t

divisors :: Int -> [Int]
divisors x =
  sort
    ( foldr
        ( flip ((<*>) . fmap (*))
            . scanl (*) 1
        )
        [1]
        (group (primeFactors x))
    )


main :: IO ()
main =
  mapM_
    ( \(s, n, xs) ->
        putStrLn $
          s
            <> ( '\n':
                 tabulated
                   10
                   (take n (filter isZumkeller xs))
               )
    )
    [ ("First 220 Zumkeller numbers:", 220, [1 ..]),
      ("First 40 odd Zumkeller numbers:", 40, [1, 3 ..])
    ]


tabulated ::
  Show a =>
  Int ->
  [a] ->
  String
tabulated nCols = go
  where
    go xs =
      let ts = show <$> xs
          w = succ (maximum (length <$> ts))
       in unlines
            ( concat
                <$> chunksOf
                  nCols
                  (justifyRight w ' ' <$> ts)
            )

justifyRight :: Int -> Char -> String -> String
justifyRight n c = (drop . length) <*> (replicate n c <>)