module ID3.Type.Frame where

import Data.Accessor
import Data.Accessor.Basic (compose)
import ID3.Type.Flags
import ID3.Type.Unparse
import ID3.Type.FrameInfo
import ID3.Parser.NativeFrames


    All ID3v2 frames consists of one frame header followed by one or more
   fields containing the actual information. The header is always 10
   bytes and laid out as follows:

     Frame ID      $xx xx xx xx  (four characters)
     Size      4 * %0xxxxxxx
     Flags         $xx xx

data ID3Frame = ID3Frame
                { frHeader_  :: FrameHeader    -- ^ frame Header
                , frInfo_    :: FrameInfo      -- ^ frame Information Value
                }   deriving Eq

emptyID3Frame = ID3Frame emptyFrameHeader (Unknown "")
initID3Frame  = flip compose emptyID3Frame

frHeader = accessor frHeader_ (\x fr -> fr {frHeader_ = x})
frInfo   = accessor frInfo_   (\x fr -> fr {frInfo_   = x})

instance HasSize ID3Frame where
    size = accessor (toInteger . length . unparse . getVal frInfo) (setVal $ frHeader .> frSize)

--content :: (FrameInfo -> a) -> Accessor ID3Frame a
--content a = accessor (\f -> a (f^.frInfo)) (\x f -> frInfo^=((f^.frInfo) {a=x}) $ f)
textContent  = frInfo .> infoTextContent

instance Show ID3Frame where
    show fr = (show $ fr^.frHeader) ++"\n"++
              (show $ fr^.frInfo  )++"\n"

{-showFrameInfo info = concatMap showRec info
        where showRec (s, bs) = "\t"++s++":\t"++bs++"\n"-}

instance Parsed ID3Frame where
    unparse fr = (unparse $ fr^.frHeader) ++
                 (unparse $ fr^.frInfo  )

type FrameName = String

{-type FrameInfo = [(String, String)]-}

{- | Frame Header
data FrameHeader = FrameHeader
                { frID_    :: FrameID         -- ^ frame ID
                , frSize_  :: FrameSize       -- ^ frame Size
                , frFlags_ :: FrameFlags      -- ^ frame Flags
                }   deriving Eq

emptyFrameHeader = FrameHeader "" 0 emptyFrameFlags
initFrameHeader  = flip compose emptyFrameHeader

frID    = accessor frID_    (\x h -> h {frID_    = x})
frSize  = accessor frSize_  (\x h -> h {frSize_  = x})
frFlags = accessor frFlags_ (\x h -> h {frFlags_ = x})

instance Show FrameHeader where
    show (FrameHeader i s fs) = "\tFrame ID:\t"++i++"\n"++
                                "\tFrame size:\t"++(show s)++
                                (show fs)

instance Parsed FrameHeader where
    unparse fh = (unparse $ Str $ fh^.frID      ) ++
                 (unparse $ fh^.frSize          ) ++
                 (unparse $ fh^.frFlags         )

{-- | /FRAME ID/

    The frame ID is made out of the characters capital A-Z and 0-9.
   Identifiers beginning with "X", "Y" and "Z" are for experimental
   frames and free for everyone to use, without the need to set the
   experimental bit in the tag header. Bear in mind that someone else
   might have used the same identifier as you. All other identifiers are
   either used or reserved for future use.
type FrameID = String

{-- | /SIZE BYTES/

   The frame ID is followed by a size descriptor containing the size of
   the data in the final frame, after encryption, compression and
   unsynchronisation. The size is excluding the frame header ('total
   frame size' - 10 bytes) and stored as a 32 bit synchsafe integer.
type FrameSize = Integer

{-- | /Frame header flags/

   In the frame header the size descriptor is followed by two flag
   bytes. All unused flags MUST be cleared. The first byte is for
   'status messages' and the second byte is a format description. If an
   unknown flag is set in the first byte the frame MUST NOT be changed
   without that bit cleared. If an unknown flag is set in the second
   byte the frame is likely to not be readable. Some flags in the second
   byte indicates that extra information is added to the header. These
   fields of extra information is ordered as the flags that indicates
   them. The flags field is defined as follows (l and o left out because
   ther resemblence to one and zero):

     %0abc0000 %0h00kmnp

   Some frame format flags indicate that additional information fields
   are added to the frame. This information is added after the frame
   header and before the frame data in the same order as the flags that
   indicates them. I.e. the four bytes of decompressed size will precede
   the encryption method byte. These additions affects the 'frame size'
   field, but are not subject to encryption or compression.

   The default status flags setting for a frame is, unless stated
   otherwise, 'preserved if tag is altered' and 'preserved if file is
   altered', i.e. @%00000000@.
data FrameFlags = FrameFlags
                  { statusF :: StatusFlags   -- ^ Frame status flags
                  , formatF :: FormatFlags   -- ^ Frame format flags
                  }     deriving Eq

emptyFrameFlags = FrameFlags emptyFlags emptyFlags
initFrameFlags  = flip compose emptyFrameFlags

status = accessor statusF (\x fs -> fs {statusF = x})
format = accessor formatF (\x fs -> fs {formatF = x})

instance Show FrameFlags where
    show fs = if not ((or $ fs^.status^.allFlags) || (or $ fs^.format^.allFlags)) then "" else
                                  (showStatusFlags $ fs^.status)++"\n"++
                                  (showFormatFlags $ fs^.format)

instance Parsed FrameFlags where
    unparse fs = (unparse $ fs^.status ) ++
                 (unparse $ fs^.format )

{-- | /Frame status flags/

    Format: @%0abc0000@ where

   a - /Tag alter preservation/

     This flag tells the tag parser what to do with this frame if it is
     unknown and the tag is altered in any way. This applies to all
     kinds of alterations, including adding more padding and reordering
     the frames.

     0     Frame should be preserved.
     1     Frame should be discarded.

   b - /File alter preservation/

     This flag tells the tag parser what to do with this frame if it is
     unknown and the file, excluding the tag, is altered. This does not
     apply when the audio is completely replaced with other audio data.

     0     Frame should be preserved.
     1     Frame should be discarded.

   c - /Read only/

      This flag, if set, tells the software that the contents of this
      frame are intended to be read only. Changing the contents might
      break something, e.g. a signature. If the contents are changed,
      without knowledge of why the frame was flagged read only and
      without taking the proper means to compensate, e.g. recalculating
      the signature, the bit MUST be cleared.
type StatusFlags = Flags

frameDiscard = accessFlag 1
fileDiscard  = accessFlag 2
readOnly     = accessFlag 3

showStatusFlags stat = if not (or $ stat^.allFlags) then "" else
                           "\t\tStatus Flags:\n"++
                           (if stat^.frameDiscard then "\t\t\t- Frame should be discarded\n" else "")++
                           (if stat^.fileDiscard  then "\t\t\t- Frame should be discarded\n" else "")++
                           (if stat^.readOnly     then "\t\t\t- Read only!\n"                else "")

{-- | /Frame format flags/

    Format: @%0h00kmnp@ where

   h - /Grouping identity/

      This flag indicates whether or not this frame belongs in a group
      with other frames. If set, a group identifier byte is added to the
      frame. Every frame with the same group identifier belongs to the
      same group.

      0     Frame does not contain group information
      1     Frame contains group information

   k - /Compression/

      This flag indicates whether or not the frame is compressed.
      A 'Data Length Indicator' byte MUST be included in the frame.

      0     Frame is not compressed.
      1     Frame is compressed using zlib [zlib] deflate method.
            If set, this requires the 'Data Length Indicator' bit
            to be set as well.

   m - /Encryption/

      This flag indicates whether or not the frame is encrypted. If set,
      one byte indicating with which method it was encrypted will be
      added to the frame. See description of the ENCR frame for more
      information about encryption method registration. Encryption
      should be done after compression. Whether or not setting this flag
      requires the presence of a 'Data Length Indicator' depends on the
      specific algorithm used.

      0     Frame is not encrypted.
      1     Frame is encrypted.

   n - /Unsynchronisation/

      This flag indicates whether or not unsynchronisation was applied
      to this frame. See section 6 for details on unsynchronisation.
      If this flag is set all data from the end of this header to the
      end of this frame has been unsynchronised. Although desirable, the
      presence of a 'Data Length Indicator' is not made mandatory by

      0     Frame has not been unsynchronised.
      1     Frame has been unsyrchronised.

   p - /Data length indicator/

      This flag indicates that a data length indicator has been added to
      the frame. The data length indicator is the value one would write
      as the 'Frame length' if all of the frame format flags were
      zeroed, represented as a 32 bit synchsafe integer.
      0      There is no Data Length Indicator.
      1      A data length Indicator has been added to the frame.
type FormatFlags = Flags

groupPart     = accessFlag 1    -- Grouping identity
compressed    = accessFlag 2    -- Compression
encrypted     = accessFlag 3    -- Encryption
unsychronised = accessFlag 4    -- Unsynchronisation
dataLengthId  = accessFlag 5    -- Data length indicator

showFormatFlags form = if not (or $ form^.allFlags) then "" else
                           "\t\tFormat Flags:\n"++
                           (if form^.groupPart     then "\t\t\t- frame is a part of group\n"        else "")++
                           (if form^.compressed    then "\t\t\t- frame is compressed\n"             else "")++
                           (if form^.encrypted     then "\t\t\t- frame is encrypted\n"              else "")++
                           (if form^.unsychronised then "\t\t\t- frame is unsynchronised\n"         else "")++
                           (if form^.dataLengthId  then "\t\t\t- frame has data length indicator\n" else "")


initFrame id = updateSize $ initID3Frame [ frHeader.>frID ^= id, frInfo ^= inf ]
    where inf = case id of
            "UFID" -> UFID "" ""
            "TXXX" -> TXXX 03 "" ""
            ('T':_)-> Text 03 ""
            "WXXX" -> WXXX 03 "" ""
            ('W':_)-> URL  ""
            --MCDI -- TODO
            --ETCO -- { format :: Integer
                -- , events :: [Event]} -- TODO
            --MLLT -- TODO
            --SYTC -- TODO
            "USLT" -> USLT 03 "eng" "" ""
            --SYLT enc lang timeFormat content descr
            "COMM" -> COMM 03 "eng" "" ""
            --RVA2 -- TODO
            --EQU2 -- TODO
            --RVRB -- TODO
            "APIC" -> APIC 03 "" 00 "" []
            --GEOB -- TODO
            "PCNT" -> PCNT 00
            "POPM" -> POPM "" 00 00
            --RBUF -- TODO
            --AENC -- TODO
            --LINK -- TODO
            --POSS -- TODO
            "USER" -> USER 03 "eng" ""
            --OWNE -- TODO
            --COMR -- TODO
            --ENCR -- TODO
            --GRID -- TODO
            --PRIV -- TODO
            --SIGN -- TODO
            --ASPI -- TODO
            "TCMP" -> TCMP False
            _      -> Unknown ""