module AWS.EC2.Types where

import Data.Default (Default(..))
import Data.Text (Text)
import Data.Time (UTCTime)

import AWS.Util

type Filter = (Text, [Text])

data Image = Image
    { imageId :: Text
    , imageLocation :: Text
    , imageImageState :: ImageState
    , imageOwnerId :: Text
    , isPublic :: Bool
    , imageProductCodes :: [ProductCode]
    , imageArchitecture :: Text
    , imageImageType :: ImageType
    , kernelId :: Maybe Text
    , ramdiskId :: Maybe Text
    , imagePlatform :: Platform
    , imageStateReason :: Maybe StateReason
    , imageOwnerAlias :: Maybe Text
    , imageName :: Text
    , imageDescription :: Text
    , imageRootDeviceType :: RootDeviceType
    , imageRootDeviceName :: Maybe Text
    , blockDeviceMappings :: [BlockDeviceMapping]
    , imageVirtualizationType :: VirtualizationType
    , imageTagSet :: [ResourceTag]
    , imageHypervisor :: Hypervisor
    }
  deriving (Show)

image
    :: Text -> Text -> ImageState -> Text -> Bool
    -> [ProductCode] -> Text -> ImageType -> Maybe Text
    -> Maybe Text -> Platform -> Maybe StateReason
    -> Maybe Text -> Text -> Text -> RootDeviceType
    -> Maybe Text -> [BlockDeviceMapping] -> VirtualizationType
    -> [ResourceTag] -> Hypervisor -> Image
image i l s oid p pc a t kid rid pf
    sr oa n d rdt rdn bdms vt ts h =
    Image
        { imageId = i
        , imageLocation = l
        , imageImageState = s
        , imageOwnerId = oid
        , isPublic = p
        , imageProductCodes = pc
        , imageArchitecture = a
        , imageImageType = t
        , kernelId = kid
        , ramdiskId = rid
        , imagePlatform = pf
        , imageStateReason = sr
        , imageOwnerAlias = oa
        , imageName = n
        , imageDescription = d
        , imageRootDeviceType = rdt
        , imageRootDeviceName = rdn
        , blockDeviceMappings = bdms
        , imageVirtualizationType = vt
        , imageTagSet = ts
        , imageHypervisor = h
        }

data ImageState
    = ImageAvailable
    | ImagePending
    | ImageFailed
  deriving (Show)

imageState :: Text -> ImageState
imageState a
    | a == "available" = ImageAvailable
    | a == "pending"   = ImagePending
    | a == "failed"    = ImageFailed
    | otherwise        = err "image state" a

data ProductCode = ProductCode
    { pcCode :: Text
    , pcType :: ProductCodeType
    }
  deriving (Show)

productCode :: Text -> ProductCodeType -> ProductCode
productCode c t = ProductCode
    { pcCode = c
    , pcType = t
    }

data ProductCodeType = Devpay
                     | Marketplace
  deriving (Show)

productCodeType :: Text -> ProductCodeType
productCodeType t
    | t == "marketplace" = Marketplace
    | t == "devpay"      = Devpay
    | otherwise          = err "product code type" t

data ImageType = Machine
               | Kernel
               | RamDisk
  deriving (Show)

imageType :: Text -> ImageType
imageType t
    | t == "machine"  = Machine
    | t == "kernel"   = Kernel
    | t == "ramdisk" = RamDisk
    | otherwise       = err "image type" t

data Platform = Windows
              | Other
  deriving (Show)

platform :: Maybe Text -> Platform
platform Nothing   = Other
platform (Just t)
    | t == "windows" = Windows
    | otherwise      = Other

data StateReason = StateReason
    { stateReasonCode :: Text
    , stateReasonMessage :: Text
    }
  deriving (Show)

stateReason :: Text -> Text -> StateReason
stateReason c m =
    StateReason
        { stateReasonCode = c
        , stateReasonMessage = m
        }

data RootDeviceType = EBS
                    | InstanceStore
  deriving (Show)

rootDeviceType :: Text -> RootDeviceType
rootDeviceType t
    | t == "ebs"            = EBS
    | t == "instance-store" = InstanceStore
    | otherwise             = err "root device type" t

data BlockDeviceMapping = BlockDeviceMapping
    { deviceName :: Text
    , virtualName :: Maybe Text
    , ebs :: Maybe EbsBlockDevice
    }
  deriving (Show)

blockDeviceMapping :: Text -> Maybe Text -> Maybe EbsBlockDevice
    -> BlockDeviceMapping
blockDeviceMapping dname v e =
    BlockDeviceMapping
        { deviceName = dname
        , virtualName = v
        , ebs = e
        }

data EbsBlockDevice = EbsBlockDevice
    { ebsSnapshotId :: Maybe Text
    , ebsVolumeSize :: Int
    , ebsDeleteOnTermination :: Bool
    , ebsVolumeType :: VolumeType
    }
  deriving (Show)

ebsBlockDevice
    :: Maybe Text -> Int -> Bool -> VolumeType
    -> EbsBlockDevice
ebsBlockDevice sid vs dot vt =
    EbsBlockDevice
        { ebsSnapshotId = sid
        , ebsVolumeSize = vs
        , ebsDeleteOnTermination = dot
        , ebsVolumeType = vt
        }

data VolumeType = Standard
                | IO1 Int
  deriving (Show)

volumeType :: Text -> Maybe Int -> VolumeType
volumeType t Nothing  | t == "standard" = Standard
volumeType t (Just i) | t == "io1"      = IO1 i
volumeType t _ = err "volume type" t

instance Default VolumeType
  where
    def = Standard

data VirtualizationType = Paravirtual
                        | HVM
  deriving (Show)

virtualizationType :: Text -> VirtualizationType
virtualizationType t
    | t == "paravirtual" = Paravirtual
    | t == "hvm"         = HVM
    | otherwise          = err "virtualization type" t

data ResourceTag = ResourceTag
    { resourceKey :: Text
    , resourceValue :: Maybe Text
    }
  deriving (Show)

resourceTag :: Text -> Maybe Text -> ResourceTag
resourceTag k v =
    ResourceTag
        { resourceKey = k
        , resourceValue = v
        }

data Hypervisor = OVM
                | Xen
  deriving (Show)

hypervisor :: Text -> Hypervisor
hypervisor t
    | t == "xen" = Xen
    | t == "ovm" = OVM
    | otherwise  = err "hypervisor" t

{- DescribeRegions -}
data Region = Region
    { regionName :: Text
    , regionEndpoint :: Text
    }
  deriving (Show)

region :: Text -> Text -> Region
region name rep = Region
    { regionName = name
    , regionEndpoint = rep
    }

{- DescribeAvailabilityZones -}
data AvailabilityZone = AvailabilityZone
    { zoneName :: Text
    , zoneState :: Text
    , zoneRegionName :: Text
    , messageSet :: [AvailabilityZoneMessage]
    }
  deriving (Show)

type AvailabilityZoneMessage = Text

availabilityZone
    :: Text -> Text -> Text -> [AvailabilityZoneMessage]
    -> AvailabilityZone
availabilityZone name st reg msgs =
    AvailabilityZone
        { zoneName = name
        , zoneState = st
        , zoneRegionName = reg
        , messageSet = msgs
        }

{- DescribeInstances -}
data Reservation = Reservation
    { reservationId :: Text
    , ownerId :: Text
    , groupSet :: [Group]
    , instanceSet :: [Instance]
    , requesterId :: Maybe Text
    }
  deriving (Show)

reservation
    :: Text -> Text -> [Group] -> [Instance] -> Maybe Text
    -> Reservation
reservation i o g iset rid = Reservation
    { reservationId = i
    , ownerId = o
    , groupSet = g
    , instanceSet = iset
    , requesterId = rid
    }

data Instance = Instance
    { instanceId :: Text
    , instanceImageId :: Text
    , instanceState :: InstanceState
    , privateDnsName :: Text
    , dnsName :: Text
    , reason :: Text
    , keyName :: Maybe Text
    , amiLaunchIndex :: Text
    , instanceProductCodes :: [ProductCode]
    , instanceType :: Text
    , launchTime :: UTCTime
    , instancePlacement :: Placement
    , instanceKernelId :: Maybe Text
    , instanceRamdiskId :: Maybe Text
    , instancePlatform :: Maybe Text
    , instanceMonitoring :: InstanceMonitoringState
    , subnetId :: Maybe Text
    , vpcId :: Maybe Text
    , privateIpAddress :: Maybe Text
    , ipAddress :: Maybe Text
    , sourceDestCheck :: Maybe Bool
    , vpcGroupSet :: [Group]
    , instanceStateReason :: Maybe StateReason
    , instanceArchitecture :: Architecture
    , instanceRootDeviceType :: RootDeviceType
    , instanceRootDeviceName :: Maybe Text
    , instanceBlockDeviceMappings :: [InstanceBlockDeviceMapping]
    , instanceInstanceLifecycle :: InstanceLifecycle
    , spotInstanceRequestId :: Maybe Text
    , instanceVirtualizationType :: VirtualizationType
    , clientToken :: Text
    , instanceTagSet :: [ResourceTag]
    , instanceHypervisor :: Hypervisor
    , instanceNetworkInterfaceSet :: [InstanceNetworkInterface]
    , instanceIamInstanceProfile :: Maybe IamInstanceProfile
    , ebsOptimized :: Bool -- default: false
    }
  deriving (Show)

ec2Instance
    :: Text -> Text -> InstanceState -> Text -> Text -> Text
    -> Maybe Text -> Text -> [ProductCode] -> Text -> UTCTime
    -> Placement -> Maybe Text -> Maybe Text -> Maybe Text
    -> InstanceMonitoringState -> Maybe Text -> Maybe Text
    -> Maybe Text -> Maybe Text -> Maybe Bool -> [Group]
    -> Maybe StateReason -> Architecture -> RootDeviceType
    -> Maybe Text -> [InstanceBlockDeviceMapping]
    -> InstanceLifecycle -> Maybe Text -> VirtualizationType
    -> Text -> [ResourceTag] -> Hypervisor
    -> [InstanceNetworkInterface] -> Maybe IamInstanceProfile
    -> Bool -> Instance
ec2Instance iid img istate pdns dns res kname aidx pcode
    itype ltime place kid rid pf mon snid vpcid paddr addr
    sdc grp sreason arch rdtype rdname bdmap life spotid
    vtype ctoken tset hv nicset iam eopt =
    Instance
        { instanceId = iid
        , instanceImageId = img
        , instanceState = istate
        , privateDnsName = pdns
        , dnsName = dns
        , reason = res
        , keyName = kname
        , amiLaunchIndex = aidx
        , instanceProductCodes = pcode
        , instanceType = itype
        , launchTime = ltime
        , instancePlacement = place
        , instanceKernelId = kid
        , instanceRamdiskId = rid
        , instancePlatform = pf
        , instanceMonitoring = mon
        , subnetId = snid
        , vpcId = vpcid
        , privateIpAddress = paddr
        , ipAddress = addr
        , sourceDestCheck = sdc
        , vpcGroupSet = grp
        , instanceStateReason = sreason
        , instanceArchitecture = arch
        , instanceRootDeviceType = rdtype
        , instanceRootDeviceName = rdname
        , instanceBlockDeviceMappings = bdmap
        , instanceInstanceLifecycle = life
        , spotInstanceRequestId = spotid
        , instanceVirtualizationType = vtype
        , clientToken = ctoken
        , instanceTagSet = tset
        , instanceHypervisor = hv
        , instanceNetworkInterfaceSet = nicset
        , instanceIamInstanceProfile = iam
        , ebsOptimized = eopt
        }

data InstanceStatus = InstanceStatus
    { isInstanceId :: Text
    , isAvailabilityZone :: Text
    , isEventsSet :: [InstanceStatusEvent]
    , isInstanceState :: InstanceState
    , isSystemStatus :: InstanceStatusType
    , isInstanceStatus :: InstanceStatusType
    }
  deriving (Show)

instanceStatus :: Text -> Text -> [InstanceStatusEvent]
    -> InstanceState -> InstanceStatusType
    -> InstanceStatusType -> InstanceStatus
instanceStatus iid az es ist sst iss = InstanceStatus
    { isInstanceId = iid
    , isAvailabilityZone = az
    , isEventsSet = es
    , isInstanceState = ist
    , isSystemStatus = sst
    , isInstanceStatus = iss
    }

data InstanceStatusEvent = InstanceStatusEvent
    { iseCode :: InstanceStatusEventCode
    , iseDescription :: Text
    , iseNotBefore :: Maybe UTCTime
    , iseNotAfter :: Maybe UTCTime
    }
  deriving (Show)

instanceStatusEvent :: InstanceStatusEventCode
    -> Text -> Maybe UTCTime -> Maybe UTCTime
    -> InstanceStatusEvent
instanceStatusEvent code desc before after = InstanceStatusEvent
    { iseCode = code
    , iseDescription = desc
    , iseNotBefore = before
    , iseNotAfter = after
    }

data InstanceStatusEventCode
    = InstanceReboot
    | InstanceStop
    | SystemReboot
    | InstanceRetirement
  deriving (Show)

instanceStatusEventCode :: Text -> InstanceStatusEventCode
instanceStatusEventCode t
    | t == "instance-reboot"     = InstanceReboot
    | t == "instance-stop"       = InstanceStop
    | t == "system-reboot"       = SystemReboot
    | t == "instance-retirement" = InstanceRetirement
    | otherwise                  = err "InstanceStatusEventCode" t

data InstanceStatusType = InstanceStatusType
    { isdStatus :: InstanceStatusTypeStatus
    , isdDetails :: [InstanceStatusDetail]
    }
  deriving (Show)

instanceStatusType :: InstanceStatusTypeStatus
    -> [InstanceStatusDetail] -> InstanceStatusType
instanceStatusType status details = InstanceStatusType
    { isdStatus = status
    , isdDetails = details
    }

data InstanceStatusTypeStatus
    = InstanceStatusOK
    | InstanceStatusImpaired
    | InstanceStatusInsufficientData
    | InstanceStatusNotApplicable
  deriving (Show)

instanceStatusTypeStatus :: Text -> InstanceStatusTypeStatus
instanceStatusTypeStatus t
    | t == "ok"                = InstanceStatusOK
    | t == "impaired"          = InstanceStatusImpaired
    | t == "insufficient-data" = InstanceStatusInsufficientData
    | t == "not-applicable"    = InstanceStatusNotApplicable
    | otherwise = err "instance status detail status" t

data InstanceStatusDetail = InstanceStatusDetail
    { isddName :: InstanceStatusDetailName
    , isddStatus :: InstanceStatusDetailStatus
    , isddImpairedSince :: Maybe UTCTime
    }
  deriving (Show)

instanceStatusDetail :: InstanceStatusDetailName
    -> InstanceStatusDetailStatus -> Maybe UTCTime
    -> InstanceStatusDetail
instanceStatusDetail name status since =
    InstanceStatusDetail
        { isddName = name
        , isddStatus = status
        , isddImpairedSince = since
        }

type InstanceStatusDetailName = Text

type InstanceStatusDetailStatus = Text

data Group = Group
    { groupId :: Text
    , groupName :: Text
    }
  deriving (Show)

group :: Text -> Text -> Group
group i n = Group
    { groupId = i
    , groupName = n
    }

data InstanceState
    = Pending
    | Running
    | ShuttingDown
    | Terminated
    | Stopping
    | Stopped
  deriving (Show)

instanceStateCodes :: [(Int, InstanceState)]
instanceStateCodes =
    [ (0, Pending)
    , (16, Running)
    , (32, ShuttingDown)
    , (48, Terminated)
    , (64, Stopping)
    , (80, Stopped)
    ]

codeToState :: Int -> InstanceState
codeToState code = case lookup code instanceStateCodes of
    Nothing -> error "invalid state code"
    Just st -> st

data Placement = Placement
    { placementAvailabilityZone :: Text
    , placementGroupName :: Text
    , tenancy :: Text
    }
  deriving (Show)

placement :: Text -> Text -> Text -> Placement
placement zone gname ten = Placement
    { placementAvailabilityZone = zone
    , placementGroupName = gname
    , tenancy = ten
    }

data InstanceMonitoringState
    = MonitoringDisabled
    | MonitoringEnabled
    | MonitoringPending
  deriving (Show)

instanceMonitoringState :: Text -> InstanceMonitoringState
instanceMonitoringState t
    | t == "disabled" = MonitoringDisabled
    | t == "enabled"  = MonitoringEnabled
    | t == "pending"  = MonitoringPending
    | otherwise       = err "monitoring state" t

data Architecture = I386 | X86_64 deriving (Show)

architecture :: Text -> Architecture
architecture t
    | t == "i386"   = I386
    | t == "x86_64" = X86_64
    | otherwise     = err "architecture" t

data InstanceBlockDeviceMapping = InstanceBlockDeviceMapping
    { instanceDeviceName :: Text
    , instanceEbs :: InstanceEbsBlockDevice
    }
  deriving (Show)

instanceBlockDeviceMapping
    :: Text -> InstanceEbsBlockDevice
    -> InstanceBlockDeviceMapping
instanceBlockDeviceMapping devname iebs =
    InstanceBlockDeviceMapping
        { instanceDeviceName = devname
        , instanceEbs = iebs
        }

data InstanceEbsBlockDevice = InstanceEbsBlockDevice
    { instanceEbsVolumeId :: Text
    , instanceEbsState :: AttachmentStatus
    , instanceEbsAttachTime :: UTCTime
    , instanceEbsDeleteOnTermination :: Bool
    }
  deriving (Show)

instanceEbsBlockDevice
    :: Text -> AttachmentStatus -> UTCTime -> Bool
    -> InstanceEbsBlockDevice
instanceEbsBlockDevice vid vst atime dot =
    InstanceEbsBlockDevice
        { instanceEbsVolumeId = vid
        , instanceEbsState = vst
        , instanceEbsAttachTime = atime
        , instanceEbsDeleteOnTermination = dot
        }

data InstanceLifecycle = LifecycleSpot | LifecycleNone
  deriving (Show)

instanceLifecycle :: Maybe Text -> InstanceLifecycle
instanceLifecycle Nothing = LifecycleNone
instanceLifecycle (Just t)
    | t == "spot"   = LifecycleSpot
    | otherwise     = err "lifecycle" t

data InstanceNetworkInterface = InstanceNetworkInterface
    { instanceNetworkInterfaceId :: Text
    , iniSubnetId :: Text
    , iniVpcId :: Text
    , iniDescription :: Text
    , iniOwnerId :: Text
    , iniStatus :: Text
    , iniPrivateIpAddress :: Text
    , iniPrivateDnsName :: Maybe Text
    , iniSourceDestCheck :: Bool
    , iniGroupSet :: [Group]
    , iniAttachment :: NetworkInterfaceAttachment
    , iniAssociation :: Maybe NetworkInterfaceAssociation
    , iniPrivateIpAddressSet :: [InstancePrivateIpAddress]
    }
  deriving (Show)

instanceNetworkInterface
    :: Text -> Text -> Text -> Text -> Text -> Text -> Text
    -> Maybe Text -> Bool -> [Group] -> NetworkInterfaceAttachment
    -> Maybe NetworkInterfaceAssociation
    -> [InstancePrivateIpAddress]
    -> InstanceNetworkInterface
instanceNetworkInterface
    iid sid vpcid desc own st paddr pdns sdc grp att asso pips =
    InstanceNetworkInterface
        { instanceNetworkInterfaceId = iid
        , iniSubnetId = sid
        , iniVpcId = vpcid
        , iniDescription = desc
        , iniOwnerId = own
        , iniStatus = st
        , iniPrivateIpAddress = paddr
        , iniPrivateDnsName = pdns
        , iniSourceDestCheck = sdc
        , iniGroupSet = grp
        , iniAttachment = att
        , iniAssociation = asso
        , iniPrivateIpAddressSet = pips
        }

data NetworkInterfaceAttachment = NetworkInterfaceAttachment
    { niatAttachmentId :: Text
    , niatDeviceIndex :: Int
    , niatStatus :: Text
    , niatAttachTime :: UTCTime
    , niatDeleteOnTermination :: Bool
    }
  deriving (Show)

networkInterfaceAttachment
    :: Text -> Int -> Text -> UTCTime -> Bool
    -> NetworkInterfaceAttachment
networkInterfaceAttachment aid idx st time dot =
    NetworkInterfaceAttachment
        { niatAttachmentId = aid
        , niatDeviceIndex = idx
        , niatStatus = st
        , niatAttachTime = time
        , niatDeleteOnTermination = dot
        }

data NetworkInterfaceAssociation = NetworkInterfaceAssociation
    { niasPublicIp :: Text
    , niasIpOwnerId :: Text
    }
  deriving (Show)

networkInterfaceAssociation
    :: Text -> Text -> NetworkInterfaceAssociation
networkInterfaceAssociation ip own =
    NetworkInterfaceAssociation
        { niasPublicIp = ip
        , niasIpOwnerId = own
        }

data InstancePrivateIpAddress = InstancePrivateIpAddress
    { iPrivateIpAddress :: Text
    , iPrimary :: Bool
    , iAssociation :: Maybe NetworkInterfaceAssociation
    }
  deriving (Show)

instancePrivateIpAddress
    :: Text -> Bool -> Maybe NetworkInterfaceAssociation
    -> InstancePrivateIpAddress
instancePrivateIpAddress ip pr asso =
    InstancePrivateIpAddress
        { iPrivateIpAddress = ip
        , iPrimary = pr
        , iAssociation = asso
        }

data IamInstanceProfile = IamInstanceProfile
    { iipArn :: Text
    , iipId :: Text
    }
  deriving (Show)

iamInstanceProfile :: Text -> Text -> IamInstanceProfile
iamInstanceProfile arn iid = IamInstanceProfile
    { iipArn = arn
    , iipId = iid
    }

data Address = Address
    { addrPublicIp :: Text
    , addrAllocationId :: Maybe Text
    , addrDomain :: AddressDomain
    , addrInstanceId :: Maybe Text
    , addrAssociationId :: Maybe Text
    , addrNetworkInterfaceId :: Maybe Text
    , addrNetworkInterfaceOwnerId :: Maybe Text
    , addrPrivateIpAddress :: Maybe Text
    }
  deriving (Show)

address :: Text -> Maybe Text -> AddressDomain -> Maybe Text
    -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Text
    -> Address
address pip alid dom iid asid niid nioid pips = Address
    { addrPublicIp = pip
    , addrAllocationId = alid
    , addrDomain = dom
    , addrInstanceId = iid
    , addrAssociationId = asid
    , addrNetworkInterfaceId = niid
    , addrNetworkInterfaceOwnerId = nioid
    , addrPrivateIpAddress = pips
    }

data AddressDomain = AddressDomainStandard | AddressDomainVPC
  deriving (Show)

addressDomain :: Maybe Text -> AddressDomain
addressDomain Nothing = AddressDomainStandard
addressDomain (Just t)
    | t == "standard" = AddressDomainStandard
    | t == "vpc"      = AddressDomainVPC
    | otherwise       = err "address domain" t

data AllocateAddressResponse = AllocateAddressResponse
    { alaPublicIp :: Text
    , alaDomain :: AddressDomain
    , alaAllocationId :: Maybe Text
    }
  deriving (Show)

allocateAddressResponse :: Text -> AddressDomain -> Maybe Text
    -> AllocateAddressResponse
allocateAddressResponse ip domain allid =
    AllocateAddressResponse
        { alaPublicIp = ip
        , alaDomain = domain
        , alaAllocationId = allid
        }

data EC2Return = EC2Success | EC2Error Text
  deriving (Show)

ec2Return :: Text -> EC2Return
ec2Return t
    | t == "true" = EC2Success
    | otherwise   = EC2Error t

data Tag = Tag
    { tagResourceId :: Text
    , tagResourceType :: Text
    , tagKey :: Text
    , tagValue :: Maybe Text
    }
  deriving (Show)

tag :: Text -> Text -> Text -> Maybe Text -> Tag
tag tid ttype key value = Tag
    { tagResourceId = tid
    , tagResourceType = ttype
    , tagKey = key
    , tagValue = value
    }

data InstanceStateChange = InstanceStateChange
    { iscInstanceId :: Text
    , iscCurrentState :: InstanceState
    , iscPreviousState :: InstanceState
    }
  deriving (Show)

instanceStateChange :: Text -> InstanceState -> InstanceState
    -> InstanceStateChange
instanceStateChange iid curr prev = InstanceStateChange
    { iscInstanceId = iid
    , iscCurrentState = curr
    , iscPreviousState = prev
    }

data ConsoleOutput = ConsoleOutput
    { coInstanceId :: Text
    , coTimestamp :: UTCTime -- ^ The time the data was last updated.
    , coOutput :: Text
    }
  deriving (Show)

consoleOutput :: Text -> UTCTime -> Text -> ConsoleOutput
consoleOutput iid time out = ConsoleOutput
    { coInstanceId = iid
    , coTimestamp = time
    , coOutput = out
    }

data PasswordData = PasswordData
    { pdInstanceId :: Text
    , pdTimestamp :: UTCTime -- ^ The time the data was last updated.
    , pdPasswordData :: Text
    }
  deriving (Show)

passwordData :: Text -> UTCTime -> Text -> PasswordData
passwordData iid time out = PasswordData
    { pdInstanceId = iid
    , pdTimestamp = time
    , pdPasswordData = out
    }

data Snapshot = Snapshot
    { snapshotId :: Text
    , ssVolumeId :: Text
    , ssStatus :: SnapshotStatus
    , ssStartTime :: UTCTime
    , ssProgress :: Text
    , ssOwnerId :: Text
    , ssVolumeSize :: Int
    , ssDescription :: Text
    , ssOwnerAlias :: Maybe Text
    , ssTagSet :: [ResourceTag]
    }
  deriving (Show)

snapshot :: Text -> Text -> SnapshotStatus -> UTCTime
    -> Text -> Text -> Int -> Text -> Maybe Text
    -> [ResourceTag] -> Snapshot
snapshot ssid vid stat stime progress oid vsize desc oali tags =
    Snapshot
    { snapshotId = ssid
    , ssVolumeId = vid
    , ssStatus = stat
    , ssStartTime = stime
    , ssProgress = progress
    , ssOwnerId = oid
    , ssVolumeSize = vsize
    , ssDescription = desc
    , ssOwnerAlias = oali
    , ssTagSet = tags
    }

data SnapshotStatus = SSPending | SSCompleted | SSError
  deriving (Show)

snapshotStatus :: Text -> SnapshotStatus
snapshotStatus t
    | t == "pending"   = SSPending
    | t == "completed" = SSCompleted
    | t == "error"     = SSError
    | otherwise        = err "snapshot status" t

data Volume = Volume
    { volumeId :: Text
    , volSize :: Int
    , volSnapshotId :: Maybe Text
    , volAvailabilityZone :: Text
    , volStatus :: VolumeStatus
    , volCreateTime :: UTCTime
    , volAttachmentSet :: [Attachment]
    , volTagSet :: [ResourceTag]
    , volVolumeType :: VolumeType
    }
  deriving (Show)

volume :: Text -> Int -> Maybe Text -> Text -> VolumeStatus -> UTCTime
    -> [Attachment] -> [ResourceTag] -> VolumeType -> Volume
volume vid size sid az stat ctime aset tset vtype = Volume
    { volumeId = vid
    , volSize = size
    , volSnapshotId = sid
    , volAvailabilityZone = az
    , volStatus = stat
    , volCreateTime = ctime
    , volAttachmentSet = aset
    , volTagSet = tset
    , volVolumeType = vtype
    }

data VolumeStatus
    = VolCreating
    | VolAvailable
    | VolInUse
    | VolDeleting
    | VolDeleted
    | VolError
  deriving (Show)

volumeStatus :: Text -> VolumeStatus
volumeStatus t
    | t == "creating"  = VolCreating
    | t == "available" = VolAvailable
    | t == "in-use"    = VolInUse
    | t == "deleting"  = VolDeleting
    | t == "deleted"   = VolDeleted
    | t == "error"     = VolError
    | otherwise        = err "volume state" t

data Attachment = Attachment
    { attVolumeId :: Text
    , attInstanceId :: Text
    , attDevice :: Text
    , attStatus :: AttachmentStatus
    , attAttachTime :: UTCTime
    , attDeleteOnTermination :: Bool
    }
  deriving (Show)

attachment :: Text -> Text -> Text -> AttachmentStatus
    -> UTCTime -> Bool -> Attachment
attachment vid iid dev stat atime dot = Attachment
    { attVolumeId = vid
    , attInstanceId = iid
    , attDevice = dev
    , attStatus = stat
    , attAttachTime = atime
    , attDeleteOnTermination = dot
    }

data AttachmentStatus
    = AttAttaching
    | AttAttached
    | AttDetaching
    | AttDetached
  deriving (Show)

attachmentStatus :: Text -> AttachmentStatus
attachmentStatus t
    | t == "attaching" = AttAttaching
    | t == "attached"  = AttAttached
    | t == "detaching" = AttDetaching
    | t == "detached"  = AttDetached
    | otherwise        = err "attachment status" t