{-# Language CPP #-}

module HsImport.ImportPos
   ( findImportPos
   , ImportPos(..)
   , matchingImports
   , bestMatchingImport
   ) where

import qualified Language.Haskell.Exts as HS
import Data.List.Index (ifoldl')
import Data.List.Split (splitOn)
import HsImport.Types

#if __GLASGOW_HASKELL__ < 710
import Control.Applicative ((<$>))
#endif

-- | Where a new import declaration should be added.
data ImportPos = Before ImportDecl -- ^ before the specified import declaration
               | After  ImportDecl -- ^ after the specified import declaration
               deriving (Show, Eq)


-- | Returns the position where the import declaration for the
--   new import should be put into the list of import declarations.
findImportPos :: ImportDecl -> [ImportDecl] -> Maybe ImportPos
findImportPos newImport imports = After <$> bestMatchingImport name imports
   where
      HS.ModuleName _ name = HS.importModule newImport


-- | Returns all import declarations having the same module name.
matchingImports :: ModuleName -> [ImportDecl] -> [ImportDecl]
matchingImports moduleName imports =
   [ i
   | i@HS.ImportDecl {HS.importModule = HS.ModuleName _ name} <- imports
   , moduleName == name
   ]


-- | Returns the best matching import declaration for the given module name.
--   E.g. if the module name is "Foo.Bar.Boo", then "Foo.Bar" is considered
--   better matching than "Foo".
bestMatchingImport :: ModuleName -> [ImportDecl] -> Maybe ImportDecl
bestMatchingImport _          []      = Nothing
bestMatchingImport moduleName imports =
   case ifoldl' computeMatches Nothing splittedMods of
        Just (idx, _) -> Just $ imports !! idx
        _             -> Nothing
   where
      computeMatches :: Maybe (Int, Int) -> Int -> [String] -> Maybe (Int, Int)
      computeMatches matches idx mod =
         let num' = numMatches splittedMod mod
             in case matches of
                     Just (_, num) | num' >= num -> Just (idx, num')
                                   | otherwise   -> matches

                     Nothing | num' > 0  -> Just (idx, num')
                             | otherwise -> Nothing
         where
            numMatches = loop 0
               where
                  loop num (a:as) (b:bs)
                     | a == b    = loop (num + 1) as bs
                     | otherwise = num

                  loop num [] _ = num
                  loop num _ [] = num

      splittedMod  = splitOn "." moduleName
      splittedMods = [ splitOn "." name
                     | HS.ImportDecl {HS.importModule = HS.ModuleName _ name} <- imports
                     ]