diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMainWithHooks defaultUserHooks
diff --git a/classify.cabal b/classify.cabal
new file mode 100644
--- /dev/null
+++ b/classify.cabal
@@ -0,0 +1,16 @@
+Name: classify
+Version: 2007.6.26
+Copyright: 2004-2007, Lemmih
+Build-Depends: base, mtl
+License: BSD3
+Author: Lemmih <lemmih@gmail.com>
+Maintainer: Lemmih <lemmih@gmail.com>
+Homepage: http://www.scannedinavian.org/~lemmih/
+Synopsis: Library for classification of files.
+Category: Tool
+HS-Source-Dirs: src
+Exposed-Modules:
+  Data.Classify.DataTypes,
+  Data.Classify.Rank,
+  Data.Classify.Parser,
+  Data.Classify.Printer
diff --git a/src/Data/Classify/DataTypes.hs b/src/Data/Classify/DataTypes.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Classify/DataTypes.hs
@@ -0,0 +1,37 @@
+module Data.Classify.DataTypes where
+
+data Element
+    = Series
+      {
+       name :: Name,
+       version :: Version,
+       title :: Title,
+       lastname :: LastName,
+       score :: Int
+      }
+    deriving (Eq,Read,Show)
+
+type Name = String
+type LastName = Name
+type Title = String
+data Version = Version
+    {
+     season :: Int,
+     episode :: Int
+    } | DateVersion
+    { year :: Int
+    , month :: Int
+    , day :: Int
+    } deriving (Ord,Eq,Read,Show)
+
+
+data MarkupStyle
+    = Literate Char
+    | Symbol Char
+    | Numeric Char Int
+      deriving (Eq)
+
+data MarkupLayout
+    = Replace Char String
+    | Function Char (Int -> String)
+
diff --git a/src/Data/Classify/Parser.hs b/src/Data/Classify/Parser.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Classify/Parser.hs
@@ -0,0 +1,264 @@
+module Data.Classify.Parser 
+--(parseElement,generateMarkup,module Parser)
+where
+
+import Data.Classify.DataTypes
+
+-- import Text.ParserCombinators.Parsec
+
+import Text.ParserCombinators.ReadP
+import Data.Char
+import Data.List
+import Data.Ord
+import Control.Monad
+--import Utils
+import System.Time
+import System.IO.Unsafe
+
+import qualified Data.Map as Map
+import qualified Data.Set as Set
+
+run readp inp
+    = let rets = map fst $ readP_to_S readp inp
+      in nub rets
+
+separator
+    = choice
+      [ do skipSpaces
+           skipMany (char '-')
+           skipSpaces
+      , do skipSpaces
+           skipMany (char '.')
+           skipSpaces
+      , do skipSpaces ]
+
+
+makeVersion s e = Version (read s) (read e)
+
+digit :: ReadP Char
+digit = satisfy isDigit
+
+oneOf :: String -> ReadP Char
+oneOf str = choice (map char str)
+
+anyChar :: ReadP Char
+anyChar = satisfy (const True)
+
+putback :: ReadP a -> ReadP a
+putback readp
+    = do inp <- look
+         choice
+           $ flip map (readP_to_S readp inp) $ \(a,str)
+             -> return a
+
+currentYear = ctYear (unsafePerformIO (toCalendarTime =<< getClockTime))
+
+parseYear = choice
+            [ do string (show currentYear)
+                 return (currentYear, 2)
+            , do string (drop 2 (show currentYear))
+                 return (currentYear, 0)
+            ]
+
+parseDateVersion
+    = choice
+      [ do (year, n) <- parseYear
+           separator
+           month <- count 2 digit
+           separator
+           day <- count 2 digit
+           return (DateVersion year (read month) (read day), n)
+      , do month <- count 2 digit
+           separator
+           day <- count 2 digit
+           separator
+           (year, n) <- parseYear
+           return (DateVersion year (read month) (read day), n)
+      ]
+
+parseVersion
+    = choice
+      [ parseDateVersion
+      , do s <- many1 digit
+           oneOf "XxEe."
+	   e <- many1 digit
+           return (makeVersion s e, 0)
+      , do s <- count 1 digit +++ count 2 digit
+           e <- count 2 digit
+           return (makeVersion s e, 0)
+      , do oneOf "sS"
+           s <- many1 digit
+           oneOf "Ee"
+           e <- many1 digit
+           return (makeVersion s e, 2)
+      , between (char '(') (char ')') parseVersion
+      , between (char '{') (char '}') parseVersion
+      , between (char '[') (char ']') parseVersion
+      ]
+
+trunc xs = [ toLower x | x <- xs, isAlphaNum x ]
+
+parseSeries known
+    = do many anyChar
+         nPre <- many1 anyChar
+         n <- Map.lookup (trunc nPre) known
+--         guard (trunc n `Map.member` known)
+         s <- look
+         guard (take 2 s /= "th") -- Fix for '24th 2007.avi'. It shouldn't parse 24th as a series.
+         many anyChar
+	 separator
+	 (v,s) <- parseVersion
+         separator
+	 (t,l) <- choice
+                  [do { t <- manyTill anyChar (do char '.'
+                                                  rest <- look
+                                                  if '.' `elem` rest
+                                                     then pfail
+                                                     else return ())
+		      ; l <- look
+		      ; return (t,l) }
+                  ,do { t <- look
+		      ; return ("",t) }]
+--         guard (null $ takeWhile isDigit $ pp t)
+--         guard (null $ takeWhile isDigit $ pp l)
+         guard (trunc l `Set.member` knownExtensions)
+         return $ Series n v (pp t) (pp l) s
+    where pp = trim . removeGarbage
+
+knownExtensions = Set.fromList $ map trunc ["avi","mpg", "mpeg", "mp4", "mov","wmv","mkw"]
+
+removeGarbage [] = []
+removeGarbage ('.':xs) = ' ':removeGarbage xs
+removeGarbage ('_':xs) = ' ':removeGarbage xs
+removeGarbage ('-':xs) = ' ':removeGarbage xs
+removeGarbage (x:xs) | isAlphaNum x || isSpace x = x:removeGarbage xs
+                     | otherwise = ' ':removeGarbage xs
+
+trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace
+
+parseElement known
+    = do choice [parseSeries known]
+
+
+
+{-
+The simpsons - 0x00 - Null
+Simpsons - S00E00 - Null
+Simpsons S0x0 Null
+S0E0 Null
+-}
+{-
+putback p
+    = do tok <- getInput
+	 r <- p
+	 setInput tok
+	 return r
+
+separator1
+    = do spaces
+	 oneOf "-"
+	 spaces
+
+separator2
+    = do many space
+	 return ()
+separator3
+    = do many1 $ oneOf "-./,[]{}_ "
+	 return ()
+
+separator
+    = choice $ map try
+      [separator3,
+       separator2,
+       separator1]
+--       return ()]
+
+makeVersion s e = Version (read s) (read e)
+parseVersion1
+    = do s <- many1 digit
+	 oneOf "XxEe."
+	 e <- many1 digit
+	 return $ makeVersion s e
+parseVersion2
+    = do oneOf "sS"
+	 s <- many1 digit
+	 oneOf "Ee"
+	 e <- many1 digit
+	 return $ makeVersion s e
+parseVersion3
+    = do (s,e) <- choice $ map try
+		  [count 2 digit >>= \s -> count 2 digit >>= \e -> return (s,e),
+		   count 1 digit >>= \s -> count 2 digit >>= \e -> return (s,e)]
+	 return $ makeVersion s e
+
+parseVersion4
+    = between (char '(') (char ')') parseVersion
+parseVersion
+    = choice $ map try
+      [parseVersion1,
+       parseVersion2,
+       parseVersion3,
+       parseVersion4]
+
+parseName :: Parser Name
+parseName = manyTill anyChar (putback $ try $ (separator >> parseVersion>>return ()))
+
+{-parseSeries1 :: Parser Element
+parseSeries1
+    = do v <- parseVersion
+	 separator
+	 n <- parseName
+	 separator
+	 t <- many (noneOf ".")
+	 l <- many anyChar
+	 return $ Series n v t l-}
+
+parseSeries2 :: Parser Element
+parseSeries2
+    = do n <- parseName
+	 separator
+	 v <- parseVersion
+         separator
+	 (t,l) <- choice $ map try
+                  [do { t <- manyTill anyChar (char '.')
+		      ; l <- getInput
+		      ; return (t,l) }
+                  ,do { t <- getInput
+		      ; return ("",t) }]
+	 return $ Series n v t l
+
+parseElement :: Parser Element
+parseElement
+    = do choice $ map try [parseSeries2]
+
+-}
+
+-----------------------------
+-- Markup Parser
+-----------------------------
+
+parseMarkupElement
+    = choice
+      [parseNumeric,
+       parseSymbol,
+       parseLiterate]
+    where parseLiterate
+	      = do lit <- anyChar
+		   return $ Literate lit
+	  parseSymbol
+	      = do char '%'
+		   sym <- anyChar
+		   return $ Symbol sym
+	  parseNumeric
+	      = do char '%'
+		   n <- many1 digit
+		   sym <- anyChar
+		   return $ Numeric sym (read n)
+
+parseMarkup = many1 parseMarkupElement
+
+generateMarkup s
+    = case readP_to_S parseMarkup s of
+        rets -> fst (head (sortBy (comparing (length.snd)) rets))
+
+
diff --git a/src/Data/Classify/Printer.hs b/src/Data/Classify/Printer.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Classify/Printer.hs
@@ -0,0 +1,59 @@
+module Data.Classify.Printer
+    ( ppElement
+    , applyMarkup
+    ) where
+
+import Data.Classify.DataTypes
+import Data.Classify.Parser
+import Data.Maybe
+import Data.List
+import Data.Char
+
+buffering :: String -> Int -> String
+buffering s len = replicate l '0'++s
+    where l = max 0 (len-length s)
+
+ppElement :: String  -- ^ Format
+          -> Element -- ^ File structure
+          -> String  -- ^ Result
+ppElement format element
+    = applyMarkup markup layout
+    where layout = elementLayout element
+          markup = generateMarkup format
+
+-----------------------------
+-- Styles
+-----------------------------
+
+elementLayout :: Element -> [MarkupLayout]
+elementLayout (s@Series {})
+    = [Replace 'S' (name s),
+       Replace 'E' (title s),
+       Replace 'l' (map toLower $ lastname s),
+       Function 's' (buffering (show $ season (version s))),
+       Function 'e' (buffering (show $ episode (version s)))]
+
+-----------------------------
+-- Markup
+-----------------------------
+
+
+applyMarkup :: [MarkupStyle] -> [MarkupLayout] -> String
+applyMarkup [] _ = ""
+applyMarkup (Literate c:xs) l = c:applyMarkup xs l
+applyMarkup (Symbol c:xs) l = s ++ applyMarkup xs l
+    where s = fromMaybe "" findReplacement
+	  findReplacement = case find findReplace l of
+			    Nothing -> Nothing
+			    Just (Replace _ s) -> Just s
+                            Just (Function _ f) -> Just (f 0)
+	      where findReplace (Replace a _) = a==c
+                    findReplace (Function a _) = a==c
+applyMarkup (Numeric c n:xs) l =numeric ++ applyMarkup xs l
+    where numeric = fromMaybe "" findNumeric
+	  findNumeric = case find findFunction l of
+			Nothing -> Nothing
+			Just (Function _ f) -> Just (f n)
+	      where findFunction (Function a _) = a==c
+		    findFunction _ = False
+
diff --git a/src/Data/Classify/Rank.hs b/src/Data/Classify/Rank.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Classify/Rank.hs
@@ -0,0 +1,84 @@
+module Data.Classify.Rank
+    ( selectBest
+    , sortByRank
+    , sortByRank'
+    ) where
+
+
+import Data.Classify.DataTypes
+import Data.List
+import Data.Char
+import Data.Ord
+
+-- For testing
+import qualified Data.Map as Map
+import qualified Data.Classify.Parser as P
+import Control.Monad
+-- import Utils
+
+selectBest :: [Element] -> Element
+selectBest = last . sortByRank
+
+sortByRank :: [Element] -> [Element]
+sortByRank = map fst . sortByRank'
+
+sortByRank' elts
+    = concatMap (sortBy (comparing (length.name.fst)))
+      $ groupBy (\a b -> snd a == snd b)
+      $ sortBy (comparing snd) $ zip elts (map getRank elts)
+
+getRank elt
+    = sum [ score elt,
+            cleanExtension ==> 2,
+            cleanTitle     ==> 1,
+            unrealVersion (version elt)
+          ]
+    where 
+          cleanExtension = all isAlpha (lastname elt)
+          cleanTitle    = all isAlpha (title elt)
+
+unrealVersion (Version season episode) = sum [ episode > 0 ==> 1
+                                             , episode > 50 ==> negate 2
+                                             , season > 0 ==> 1
+                                             , season > 30 ==> negate 1
+                                             ]
+unrealVersion (DateVersion year month day) = 0
+
+infix 2 ==>
+True ==> n = n
+False ==> _ = 0
+--------------------------------------------------------------
+-- Tests
+--------------------------------------------------------------
+
+testSeries = Map.fromList [ (P.trunc t,t) | (_,t,_) <- testData]
+
+testData = [ ("24.S06E01.6AM.TO.7AM.PROPER.DVDRip.XviD-MEMETiC.avi", "24", Version 6 1)
+           , ("The.Simpsons.S18E09.PROPER.PDTV.XviD-2HD.avi", "The Simpsons", Version 18 9)
+           , ("30.Rock.S01E09.HDTV.XviD-NoTV.avi", "30 Rock", Version 1 9)
+           , ("Cops.S19E32.HDTV.XviD-2HD.avi", "Cops", Version 19 32)
+           , ("The.4400.S04E02.DSR.XviD-ORENJi.avi", "The 4400", Version 4 2)
+           , ("Jay.Leno.2007.06.25.John.Edwards.HDTV.XViD-STFU.avi", "Jay Leno", DateVersion 2007 6 25)
+           , ("the.daily.show.06.21.07.dsr.xvid-crimson.[VTV].avi", "The Daily Show", DateVersion 2007 6 21)
+           , ("the.colbert.report.06.21.07.dsr.xvid-w4f.[VTV].avi", "The Colbert Report", DateVersion 2007 6 21)
+           , ("hidden.palms.0107-yestv.[VTV].avi", "Hidden Palms", Version 1 7)
+           , ("hidden.palms.0108-yestv.[VTV].avi", "Hidden Palms", Version 1 8)
+           , ("hidden.palms.0109-yestv.[VTV].avi", "Hidden Palms", Version 1 9)
+           , ("hidden.palms.0110-yestv.[VTV].avi", "Hidden Palms", Version 1 10)
+           , ("hidden.palms.0111-yestv.[VTV].avi", "Hidden Palms", Version 1 11)
+           ]
+
+runTests = flip mapM_ testData $ \(str, series, expectedVersion) ->
+           let elts = P.run (P.parseElement testSeries) str
+           in if null elts
+              then do putStrLn $ "Failed to parse: " ++ str
+                      putStrLn $ "Expected: " ++ show (series, expectedVersion)
+              else let best = selectBest elts
+                       v = version best
+                   in when (version best /= expectedVersion) $
+                      do forM (sortByRank elts) $ \elt ->
+                             putStrLn $ "Erroneous parse: " ++ show (str, title elt, version elt)
+                         putStrLn $ "Expected: " ++ show (series, expectedVersion)
+
+
+
