import           Data.List   (nub, tails)
import           Parsing     (splitByString)

type Line = ((Int, Int), (Int, Int))

main :: IO ()
main = do
  input <- getContents
  let
    vents = parseVents input
  putStrLn (show (solution1 vents))

solution1 :: [Line] -> Int
solution1 = length . nub . intersectAll . filterDiagonal

intersectAll :: [Line] -> [(Int, Int)]
intersectAll =
  flatten . (map intersectOne) . (combinations 2)
  where
    intersectOne :: [Line] -> [(Int, Int)]
    intersectOne []            = []
    intersectOne (vent:others) = flatten (map (intersect vent) others)

intersect :: Line -> Line -> [(Int, Int)]
intersect ((x1, y1), (x1', y1')) ((x2, y2), (x2', y2'))
  -- Colinear - In these cases, we may have multiple intersections
  | all (== x1) [x1', x2, x2'] =
    (map (\y -> (x1,y)) (intersect1D (sortedTuple (y1, y1')) (sortedTuple (y2, y2'))))
  | all (== y1) [y1', y2, y2'] =
    (map (\x -> (x,y1)) (intersect1D (sortedTuple (x1, x1')) (sortedTuple (x2, x2'))))
  -- Not colinear - We can find out if these intersect by simply
  -- checking whether the second line goes through the
  -- horizontal/vertical range of the first (whichever line the first
  -- segment lives on)
  | (x1 == x1' && y2 == y2') =
    if (min x2 x2') <= x1 && (max x2 x2') >= x1 && (min y1 y1') <= y2 && (max y1 y1') >= y2
    then
      [(x1,y2)]
    else
      []
  | (y1 == y1' && x2 == x2') =
    if (min y2 y2') <= y1 && (max y2 y2') >= y1 && (min x1 x1') <= x2 && (max x1 x1') >= x2
    then
      [(x2,y1)]
    else
      []
  -- In all other cases, we consider the lines not to intersect, since
  -- diagonal lines are out of scope
  | otherwise = []
  where
    sortedTuple :: (Int, Int) -> (Int, Int)
    sortedTuple (a, b)
      | a > b = (b, a)
      | otherwise = (a, b)
    intersect1D :: (Int, Int) -> (Int, Int) -> [Int]
    intersect1D (a, b) (c, d) =
      let
        start = max a c
        end = min b d
      in
        [start..end]

filterDiagonal :: [Line] -> [Line]
filterDiagonal = filter (not . isDiagonal)
  where
    isDiagonal :: Line -> Bool
    isDiagonal ((x, y), (x', y')) = x /= x' && y /= y'

parseVents :: String -> [Line]
parseVents = tupelize . splitUp
  where
    tupelize :: [[[Int]]] -> [Line]
    tupelize = map subTupelize
      where
        subTupelize :: [[Int]] -> Line
        subTupelize [[a, b], [c, d]] = ((a, b), (c, d))
        -- For our parsing purposes, we don't need to cover error
        -- cases
        subTupelize _                = undefined
    splitUp :: String -> [[[Int]]]
    splitUp = (map (map (map read))) . (map (map (splitByString ","))) . (map (splitByString " -> ")) . lines

-- Missing stdlib functions

combinations :: Int -> [a] -> [[a]]
combinations 0 _  = [[]]
combinations n xs = [y:ys | y:xs' <- tails xs
                          , ys <- combinations (n-1) xs']

flatten :: [[a]] -> [a]
flatten = foldr1 (++)

-- Tests

testInput1 :: String
testInput1 = unlines [
  "0,9 -> 5,9",
  "8,0 -> 0,8",
  "9,4 -> 3,4",
  "2,2 -> 2,1",
  "7,0 -> 7,4",
  "6,4 -> 2,0",
  "0,9 -> 2,9",
  "3,4 -> 1,4",
  "0,0 -> 8,8",
  "5,5 -> 8,2"
  ]

test1 = solution1 (parseVents testInput1)

testIntersect1 = intersect ((0, 0), (0, 5)) ((0, 2), (0, 4)) == [(0, 2), (0, 3), (0, 4)]
testIntersect2 = intersect ((0, 0), (0, 5)) ((0, 5), (0, 6)) == [(0, 5)]
testIntersect3 = intersect ((0, 0), (0, 5)) ((0, 6), (0, 7)) == []
testIntersect4 = intersect ((0, 0), (5, 0)) ((2, 0), (4, 0)) == [(2, 0), (3, 0), (4, 0)]
testIntersect5 = intersect ((0, 0), (0, 5)) ((0, 2), (4, 2)) == [(0, 2)]
testIntersect6 = intersect ((0, 0), (0, 5)) ((0, 3), (4, 3)) == [(0, 3)]
testIntersect7 = intersect ((1, 0), (1, 5)) ((0, 2), (4, 2)) == [(1, 2)]
testIntersect8 = intersect ((4, 0), (4, 5)) ((0, 2), (4, 2)) == [(4, 2)]
testIntersect9 = intersect ((1, 0), (1, 5)) ((2, 2), (4, 2)) == []
testIntersect10 = intersect ((9, 4), (3, 4)) ((7, 0), (7, 4)) == [(7, 4)]
testIntersect11 = intersect ((2, 2), (2, 1)) ((3, 4), (1, 4)) == []
testIntersect12 = intersect ((0, 9), (5, 9)) ((7, 0), (7, 4)) == []

testIntersect =
  all (== True) [
  testIntersect1,
  testIntersect2,
  testIntersect3,
  testIntersect4,
  testIntersect5,
  testIntersect6,
  testIntersect7,
  testIntersect8,
  testIntersect9
  ]