--
-- MinIO Haskell SDK, (C) 2017 MinIO, Inc.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--

module Network.Minio.XmlGenerator
  ( mkCreateBucketConfig,
    mkCompleteMultipartUploadRequest,
    mkPutNotificationRequest,
    mkSelectRequest,
  )
where

import qualified Data.ByteString.Lazy as LBS
import qualified Data.HashMap.Strict as H
import qualified Data.Text as T
import Lib.Prelude
import Network.Minio.Data
import Text.XML

-- | Create a bucketConfig request body XML
mkCreateBucketConfig :: Text -> Region -> ByteString
mkCreateBucketConfig ns location = LBS.toStrict $ renderLBS def bucketConfig
  where
    s3Element n = Element (s3Name ns n) mempty
    root =
      s3Element
        "CreateBucketConfiguration"
        [ NodeElement $
            s3Element
              "LocationConstraint"
              [NodeContent location]
        ]
    bucketConfig = Document (Prologue [] Nothing []) root []

-- | Create a completeMultipartUpload request body XML
mkCompleteMultipartUploadRequest :: [PartTuple] -> ByteString
mkCompleteMultipartUploadRequest partInfo =
  LBS.toStrict $ renderLBS def cmur
  where
    root =
      Element "CompleteMultipartUpload" mempty $
        map (NodeElement . mkPart) partInfo
    mkPart (n, etag) =
      Element
        "Part"
        mempty
        [ NodeElement $
            Element
              "PartNumber"
              mempty
              [NodeContent $ T.pack $ show n],
          NodeElement $
            Element
              "ETag"
              mempty
              [NodeContent etag]
        ]
    cmur = Document (Prologue [] Nothing []) root []

-- Simplified XML representation without element attributes.
data XNode
  = XNode Text [XNode]
  | XLeaf Text Text
  deriving (Eq, Show)

toXML :: Text -> XNode -> ByteString
toXML ns node =
  LBS.toStrict $ renderLBS def $
    Document (Prologue [] Nothing []) (xmlNode node) []
  where
    xmlNode :: XNode -> Element
    xmlNode (XNode name nodes) =
      Element (s3Name ns name) mempty $
        map (NodeElement . xmlNode) nodes
    xmlNode (XLeaf name content) =
      Element
        (s3Name ns name)
        mempty
        [NodeContent content]

class ToXNode a where
  toXNode :: a -> XNode

instance ToXNode Event where
  toXNode = XLeaf "Event" . show

instance ToXNode Notification where
  toXNode (Notification qc tc lc) =
    XNode "NotificationConfiguration" $
      map (toXNodesWithArnName "QueueConfiguration" "Queue") qc
        ++ map (toXNodesWithArnName "TopicConfiguration" "Topic") tc
        ++ map (toXNodesWithArnName "CloudFunctionConfiguration" "CloudFunction") lc

toXNodesWithArnName :: Text -> Text -> NotificationConfig -> XNode
toXNodesWithArnName eltName arnName (NotificationConfig id arn events fRule) =
  XNode eltName $
    [XLeaf "Id" id, XLeaf arnName arn] ++ map toXNode events
      ++ [toXNode fRule]

instance ToXNode Filter where
  toXNode (Filter (FilterKey (FilterRules rules))) =
    XNode "Filter" [XNode "S3Key" (map getFRXNode rules)]

getFRXNode :: FilterRule -> XNode
getFRXNode (FilterRule n v) =
  XNode
    "FilterRule"
    [ XLeaf "Name" n,
      XLeaf "Value" v
    ]

mkPutNotificationRequest :: Text -> Notification -> ByteString
mkPutNotificationRequest ns = toXML ns . toXNode

mkSelectRequest :: SelectRequest -> ByteString
mkSelectRequest r = LBS.toStrict $ renderLBS def sr
  where
    sr = Document (Prologue [] Nothing []) root []
    root =
      Element "SelectRequest" mempty $
        [ NodeElement
            ( Element
                "Expression"
                mempty
                [NodeContent $ srExpression r]
            ),
          NodeElement
            ( Element
                "ExpressionType"
                mempty
                [NodeContent $ show $ srExpressionType r]
            ),
          NodeElement
            ( Element "InputSerialization" mempty
                $ inputSerializationNodes
                $ srInputSerialization r
            ),
          NodeElement
            ( Element "OutputSerialization" mempty
                $ outputSerializationNodes
                $ srOutputSerialization r
            )
        ]
          ++ maybe [] reqProgElem (srRequestProgressEnabled r)
    reqProgElem enabled =
      [ NodeElement
          ( Element
              "RequestProgress"
              mempty
              [ NodeElement
                  ( Element
                      "Enabled"
                      mempty
                      [ NodeContent
                          (if enabled then "TRUE" else "FALSE")
                      ]
                  )
              ]
          )
      ]
    inputSerializationNodes is =
      comprTypeNode (isCompressionType is)
        ++ [NodeElement $ formatNode (isFormatInfo is)]
    comprTypeNode (Just c) =
      [ NodeElement $
          Element
            "CompressionType"
            mempty
            [ NodeContent $ case c of
                CompressionTypeNone -> "NONE"
                CompressionTypeGzip -> "GZIP"
                CompressionTypeBzip2 -> "BZIP2"
            ]
      ]
    comprTypeNode Nothing = []
    kvElement (k, v) = Element (Name k Nothing Nothing) mempty [NodeContent v]
    formatNode (InputFormatCSV (CSVProp h)) =
      Element
        "CSV"
        mempty
        (map NodeElement $ map kvElement $ H.toList h)
    formatNode (InputFormatJSON p) =
      Element
        "JSON"
        mempty
        [ NodeElement
            ( Element
                "Type"
                mempty
                [ NodeContent $ case jsonipType p of
                    JSONTypeDocument -> "DOCUMENT"
                    JSONTypeLines -> "LINES"
                ]
            )
        ]
    formatNode InputFormatParquet = Element "Parquet" mempty []
    outputSerializationNodes (OutputSerializationJSON j) =
      [ NodeElement
          ( Element "JSON" mempty
              $ rdElem
              $ jsonopRecordDelimiter j
          )
      ]
    outputSerializationNodes (OutputSerializationCSV (CSVProp h)) =
      [ NodeElement $
          Element
            "CSV"
            mempty
            (map NodeElement $ map kvElement $ H.toList h)
      ]
    rdElem Nothing = []
    rdElem (Just t) =
      [ NodeElement $
          Element
            "RecordDelimiter"
            mempty
            [NodeContent t]
      ]