module Data.PropertyList
    ( -- * The basic property list types
      -- |Property lists have several supported representations, but the main
      -- one most end users will care about is 'PropertyList'.  This is a basic
      -- algebraic representation of property lists which can be constructed
      -- using the polymorphic constructors described below and pattern-matched
      -- using the polymorphic destructors (designed for convenient usage with
      -- the ViewPatterns extension)
      --
      -- Also provided is the 'PartialPropertyList' representation, which is 
      -- a property list that is extended by adding a new constructor containing
      -- some arbitrary type chosen by the user.  It is mostly used as an
      -- intermediate represenation for property lists that have been parsed
      -- into their overall shape but not all elements have been parsed into
      -- their final format.
      -- 
      -- The 'UnparsedXmlPlistItem' and 'UnparsedBPListRecord' types are used
      -- with 'PartialPropertyList', and that is really their only purpose - to
      -- represent unparseable items during intermediate stages of translation.
      PropertyList
    , PartialPropertyList
    , UnparsedBPListRecord(..)
    , UnparsedXmlPlistItem(..)
      
    -- * Constructors and destructors for property lists
    -- |The \"pl*\" operations construct 'PropertyList's, 'PartialPropertyList's, 
    -- or any other types defining an unlifted algebra for the 'PropertyListS'
    -- signature.
    --
    -- The \"fromPl*\" operations are ViewPattern matching operations for
    -- 'PropertyList', 'PartialPropertyList', or any other type defining
    -- a 'Maybe'-lifted coalgebra for 'PropertyListS'.
    --
    -- The generality of these operations means that they can also be used
    -- to directly generate or analyze \"external\" formats such as the XML
    -- 'Plist' representation.
    , plArray,   fromPlArray
    , plData,    fromPlData
    , plDate,    fromPlDate
    , plDict,    fromPlDict
    , plReal,    fromPlReal
    , plInt,     fromPlInt
    , plString,  fromPlString
    , plBool,    fromPlBool
    
    -- * The internal algebraic model for property lists
    -- | Internally, conversions between various property list representations 
    -- are all defined in terms of universal algebra, which is basically just
    -- fancy math-talk for \"very general interfaces that let you convert between 
    -- certain kinds of representations easily and efficiently\".  
    -- 
    -- Most users do not need to understand this stuff - the class names are
    -- only exported because they appear in the types of the constructors and
    -- destructors.  For more detailed info, see "Data.PropertyList.Algebra".
    , PListAlgebra, PListCoalgebra
    , InitialPList, TerminalPList

    -- * Parsing and formatting property lists using any supported format
    , readPropertyListFromFile
    , writePropertyListToFile
    
    -- * Parsing and formatting property lists using the Binary format
    , readBinaryPropertyList
    , encodeBinaryPropertyList
    
    , readBinaryPropertyListFromFile
    , writeBinaryPropertyListToFile
    
    -- * Parsing and formatting property lists using the XML format
    , readXmlPropertyList
    , showXmlPropertyList
    
    , readXmlPropertyListFromFile
    , writeXmlPropertyListToFile
    
    -- * Manipulating property lists
    -- 
    -- |This module exports a class ('PropertyListItem') and several functions
    -- used to manipulate 'PropertyList's and their contents at a high level,
    -- viewing and manipulating the data in the tree through ad-hoc 
    -- transformations.
    -- 
    -- For example, consider the following property list:
    --
    -- > myPlist = plDict $ M.fromList
    -- >    [ ("foo", plInt 4)
    -- >    , ("bar", plString "qux")
    -- >    , ("subDict", plDict $ M.fromList
    -- >         [ ("item 1", plString "This is item 1!")
    -- >         , ("item B", plBool True)
    -- >         ])
    -- >    ] :: PropertyList
    -- 
    -- Some typical actions you might do with a plist like this (Note that
    -- in many cases a 'Just' is added - this is because the key-path operations
    -- are defined in terms of 'Maybe' so that operations like 'setItemAtKeyPath'
    -- or 'alterItemAtKeyPath' can create new items where none already exist):
    -- 
    -- > getItemAtKeyPath ["subDict", "item B"] (Just myPlist) :: Maybe Bool
    -- 
    --      (returns @Just True@)
    -- 
    -- > getItemAtKeyPath ["subDict"] (Just myPlist) :: Maybe (M.Map String String)
    -- 
    --      (returns @Just (M.fromList [(\"item 1\", \"This is item 1!\"), (\"item B\", \"YES\")])@.  
    --       Note the stringification of non-string items.  In general, 'PropertyListItem'
    --       instances are expected to do \"reasonable\" conversions to try and make sense
    --       of what the user is asking the system to do.)
    -- 
    -- > setItemAtKeyPath ["omg", "lolwut"] (Just "roflcopter") (Just myPlist)
    -- 
    --      (returns a modified version of @myPlist@ with 
    --      @plDict $ M.fromList [(\"omg\",
    --         plDict $ M.fromList [(\"lolwut\",
    --           plString \"roflcopter\")])]@
    --       added to the root dictionary)
    --
    -- > setItemAtKeyPath ["foo"] Nothing (Just myPlist)
    -- 
    --      (returns a modified version of @myPlist@ with the \"foo\" entry in the
    --       root dictionary deleted)
    -- 
    -- > setItemAtKeyPath ["foo", "bar", "baz"] (Just "qux") Nothing
    -- 
    --      (returns a new dictionary with @plString \"qux\"@ at the key path foo.bar.baz)
    --

    , module Data.PropertyList.PropertyListItem
    , module Data.PropertyList.KeyPath
    ) where

import Control.Exception (try, SomeException(..))

import Data.PropertyList.Algebra
import Data.PropertyList.Binary
import Data.PropertyList.Types
import Data.PropertyList.Xml

import Data.PropertyList.PropertyListItem
import Data.PropertyList.KeyPath

-- | Read a property list from a file, trying all supported property list formats.
-- Presently, the \"XML1\" and \"bplist00\" formats are supported.  See also
-- 'readXmlPropertyListFromFile' and 'readBinaryPropertyListFromFile'.
readPropertyListFromFile :: FilePath -> IO PropertyList
readPropertyListFromFile file = do
    partial <- readPartial file
    case partial of
        Left  xml -> completePropertyListByM barf xml
        Right bin -> completePropertyListByM barf bin
    where
        readPartial :: FilePath -> IO (Either
            (PartialPropertyList UnparsedXmlPlistItem)
            (PartialPropertyList UnparsedBPListRecord))
        readPartial file = do
            mbPartial <- try (readBinaryPartialPropertyListFromFile file)
            case mbPartial of
                Left SomeException{} -> fmap Left (readXmlPartialPropertyListFromFile file)
                Right bin            -> return (Right bin)
        
        barf :: Show a => a -> IO PropertyList
        barf unparsed = fail ("Unparseable item found: " ++ show unparsed)

-- | Write a property list to a file, using a \"preferred\" property list format.
-- Presently, that is the \"XML1\" format.  See also 'writeXmlPropertyListToFile'.
writePropertyListToFile :: FilePath -> PropertyList -> IO ()
writePropertyListToFile = writeXmlPropertyListToFile