content: module Rattletrap.Console.Main where
  
  import qualified Control.Exception as Exception
  import qualified Control.Monad as Monad
  import qualified Data.Bool as Bool
  import qualified Data.ByteString as ByteString
  import qualified Data.ByteString.Lazy as LazyByteString
  import qualified Data.List as List
  import qualified Data.Text as Text
  import qualified Network.HTTP.Client as Client
  import qualified Network.HTTP.Client.TLS as Client
  import qualified Rattletrap.Console.Config as Config
  import qualified Rattletrap.Console.Mode as Mode
  import qualified Rattletrap.Console.Option as Option
  import qualified Rattletrap.Schema as Schema
  import qualified Rattletrap.Type.Attribute as Attribute
  import qualified Rattletrap.Type.Attribute.AppliedDamage as Attribute.AppliedDamage
  import qualified Rattletrap.Type.Attribute.Boolean as Attribute.Boolean
  import qualified Rattletrap.Type.Attribute.Boost as Attribute.Boost
  import qualified Rattletrap.Type.Attribute.Byte as Attribute.Byte
  import qualified Rattletrap.Type.Attribute.CamSettings as Attribute.CamSettings
  import qualified Rattletrap.Type.Attribute.ClubColors as Attribute.ClubColors
  import qualified Rattletrap.Type.Attribute.CustomDemolish as Attribute.CustomDemolish
  import qualified Rattletrap.Type.Attribute.CustomDemolishExtended as Attribute.CustomDemolishExtended
  import qualified Rattletrap.Type.Attribute.DamageState as Attribute.DamageState
  import qualified Rattletrap.Type.Attribute.Demolish as Attribute.Demolish
  import qualified Rattletrap.Type.Attribute.Enum as Attribute.Enum
  import qualified Rattletrap.Type.Attribute.Explosion as Attribute.Explosion
  import qualified Rattletrap.Type.Attribute.ExtendedExplosion as Attribute.ExtendedExplosion
  import qualified Rattletrap.Type.Attribute.FlaggedByte as Attribute.FlaggedByte
  import qualified Rattletrap.Type.Attribute.FlaggedInt as Attribute.FlaggedInt
  import qualified Rattletrap.Type.Attribute.Float as Attribute.Float
  import qualified Rattletrap.Type.Attribute.GameMode as Attribute.GameMode
  import qualified Rattletrap.Type.Attribute.GameServer as Attribute.GameServer
  import qualified Rattletrap.Type.Attribute.Int as Attribute.Int
  import qualified Rattletrap.Type.Attribute.Int64 as Attribute.Int64
  import qualified Rattletrap.Type.Attribute.Loadout as Attribute.Loadout
  import qualified Rattletrap.Type.Attribute.LoadoutOnline as Attribute.LoadoutOnline
  import qualified Rattletrap.Type.Attribute.Loadouts as Attribute.Loadouts
  import qualified Rattletrap.Type.Attribute.LoadoutsOnline as Attribute.LoadoutsOnline
  import qualified Rattletrap.Type.Attribute.Location as Attribute.Location
  import qualified Rattletrap.Type.Attribute.MusicStinger as Attribute.MusicStinger
  import qualified Rattletrap.Type.Attribute.PartyLeader as Attribute.PartyLeader
  import qualified Rattletrap.Type.Attribute.Pickup as Attribute.Pickup
  import qualified Rattletrap.Type.Attribute.PickupInfo as Attribute.PickupInfo
  import qualified Rattletrap.Type.Attribute.PickupNew as Attribute.PickupNew
  import qualified Rattletrap.Type.Attribute.PlayerHistoryKey as Attribute.PlayerHistoryKey
  import qualified Rattletrap.Type.Attribute.PrivateMatchSettings as Attribute.PrivateMatchSettings
  import qualified Rattletrap.Type.Attribute.Product as Attribute.Product
  import qualified Rattletrap.Type.Attribute.ProductValue as Attribute.ProductValue
  import qualified Rattletrap.Type.Attribute.QWord as Attribute.QWord
  import qualified Rattletrap.Type.Attribute.RepStatTitle as Attribute.RepStatTitle
  import qualified Rattletrap.Type.Attribute.Reservation as Attribute.Reservation
  import qualified Rattletrap.Type.Attribute.RigidBodyState as Attribute.RigidBodyState
  import qualified Rattletrap.Type.Attribute.Rotation as Attribute.Rotation
  import qualified Rattletrap.Type.Attribute.StatEvent as Attribute.StatEvent
  import qualified Rattletrap.Type.Attribute.String as Attribute.String
  import qualified Rattletrap.Type.Attribute.TeamPaint as Attribute.TeamPaint
  import qualified Rattletrap.Type.Attribute.Title as Attribute.Title
  import qualified Rattletrap.Type.Attribute.UniqueId as Attribute.UniqueId
  import qualified Rattletrap.Type.Attribute.WeldedInfo as Attribute.WeldedInfo
  import qualified Rattletrap.Type.AttributeMapping as AttributeMapping
  import qualified Rattletrap.Type.AttributeValue as AttributeValue
  import qualified Rattletrap.Type.Cache as Cache
  import qualified Rattletrap.Type.ClassMapping as ClassMapping
  import qualified Rattletrap.Type.CompressedWord as CompressedWord
  import qualified Rattletrap.Type.CompressedWordVector as CompressedWordVector
  import qualified Rattletrap.Type.Content as Content
  import qualified Rattletrap.Type.Dictionary as Dictionary
  import qualified Rattletrap.Type.F32 as F32
  import qualified Rattletrap.Type.Frame as Frame
  import qualified Rattletrap.Type.Header as Header
  import qualified Rattletrap.Type.I32 as I32
  import qualified Rattletrap.Type.I64 as I64
  import qualified Rattletrap.Type.I8 as I8
  import qualified Rattletrap.Type.Initialization as Initialization
  import qualified Rattletrap.Type.Int8Vector as Int8Vector
  import qualified Rattletrap.Type.Keyframe as Keyframe
  import qualified Rattletrap.Type.List as RList
  import qualified Rattletrap.Type.Mark as Mark
  import qualified Rattletrap.Type.Message as Message
  import qualified Rattletrap.Type.ObjectTarget as ObjectTarget
  import qualified Rattletrap.Type.Property as Property
  import qualified Rattletrap.Type.Property.Array as Property.Array
  import qualified Rattletrap.Type.Property.Byte as Property.Byte
  import qualified Rattletrap.Type.Property.Struct as Property.Struct
  import qualified Rattletrap.Type.PropertyValue as PropertyValue
  import qualified Rattletrap.Type.Quaternion as Quaternion
  import qualified Rattletrap.Type.RemoteId as RemoteId
  import qualified Rattletrap.Type.RemoteId.PlayStation as RemoteId.PlayStation
  import qualified Rattletrap.Type.RemoteId.PsyNet as RemoteId.PsyNet
  import qualified Rattletrap.Type.RemoteId.Switch as RemoteId.Switch
  import qualified Rattletrap.Type.Replay as Replay
  import qualified Rattletrap.Type.Replication as Replication
  import qualified Rattletrap.Type.Replication.Destroyed as Replication.Destroyed
  import qualified Rattletrap.Type.Replication.Spawned as Replication.Spawned
  import qualified Rattletrap.Type.Replication.Updated as Replication.Updated
  import qualified Rattletrap.Type.ReplicationValue as ReplicationValue
  import qualified Rattletrap.Type.Rotation as Rotation
  import qualified Rattletrap.Type.Section as Section
  import qualified Rattletrap.Type.Str as Str
  import qualified Rattletrap.Type.U32 as U32
  import qualified Rattletrap.Type.U64 as U64
  import qualified Rattletrap.Type.U8 as U8
  import qualified Rattletrap.Type.Vector as Vector
  import qualified Rattletrap.Utility.Helper as Rattletrap
  import qualified Rattletrap.Utility.Json as Json
  import qualified Rattletrap.Version as Version
  import qualified System.Console.GetOpt as Console
  import qualified System.Environment as Environment
  import qualified System.Exit as Exit
  import qualified System.FilePath as FilePath
  import qualified System.IO as IO
  
  main :: IO ()
  main = do
    name <- Environment.getProgName
    arguments <- Environment.getArgs
    rattletrap name arguments
  
  rattletrap :: String -> [String] -> IO ()
  rattletrap name arguments = do
    config <- getConfig arguments
    if Config.help config
      then helpMain name
      else
        if Config.version config
          then versionMain
          else
            if Config.schema config
              then schemaMain config
              else defaultMain name config
  
  helpMain :: String -> IO ()
  helpMain name = do
    putStr $
      Console.usageInfo
        (unwords [name, "version", Version.string])
        Option.all
  
  versionMain :: IO ()
  versionMain = do
    putStrLn Version.string
  
  schemaMain :: Config.Config -> IO ()
  schemaMain config = putOutput config $ encodeJson config schema
  
  defaultMain :: String -> Config.Config -> IO ()
  defaultMain name config = do
    input <- getInput name config
    let decode = getDecoder config
    replay <- case decode input of
      Left (ls, e) -> do
        IO.hPutStr IO.stderr $
          unlines
            [ "ERROR: " <> Exception.displayException e,
              "-- Context: " <> List.intercalate ", " ls,
              "-- You are using Rattletrap version " <> Version.string,
              "-- " <> show config,
              "-- Please report this problem at https://github.com/tfausak/rattletrap/issues/new"
            ]
        Exit.exitFailure
      Right x -> pure x
    let encode = getEncoder config
    putOutput config (encode replay)
  
  schema :: Json.Value
  schema =
    let contentSchema = Content.schema $ RList.schema Frame.schema
     in Json.object
          [ Json.pair "$schema" "http://json-schema.org/draft-07/schema",
            Json.pair "$id" Replay.schemaUrl,
            Json.pair "$ref" "#/definitions/replay",
            Json.pair "definitions" . Json.object $
              fmap
                (\s -> Json.pair (Text.unpack $ Schema.name s) $ Schema.json s)
                [ Attribute.schema,
                  Attribute.AppliedDamage.schema,
                  Attribute.Boolean.schema,
                  Attribute.Boost.schema,
                  Attribute.Byte.schema,
                  Attribute.CamSettings.schema,
                  Attribute.ClubColors.schema,
                  Attribute.CustomDemolish.schema,
                  Attribute.CustomDemolishExtended.schema,
                  Attribute.DamageState.schema,
                  Attribute.Demolish.schema,
                  Attribute.Enum.schema,
                  Attribute.Explosion.schema,
                  Attribute.ExtendedExplosion.schema,
                  Attribute.FlaggedByte.schema,
                  Attribute.FlaggedInt.schema,
                  Attribute.Float.schema,
                  Attribute.GameMode.schema,
                  Attribute.GameServer.schema,
                  Attribute.Int.schema,
                  Attribute.Int64.schema,
                  Attribute.Loadout.schema,
                  Attribute.LoadoutOnline.schema,
                  Attribute.Loadouts.schema,
                  Attribute.LoadoutsOnline.schema,
                  Attribute.Location.schema,
                  Attribute.MusicStinger.schema,
                  Attribute.PartyLeader.schema,
                  Attribute.Pickup.schema,
                  Attribute.PickupInfo.schema,
                  Attribute.PickupNew.schema,
                  Attribute.PlayerHistoryKey.schema,
                  Attribute.PrivateMatchSettings.schema,
                  Attribute.Product.schema,
                  Attribute.ProductValue.schema,
                  Attribute.QWord.schema,
                  Attribute.RepStatTitle.schema,
                  Attribute.Reservation.schema,
                  Attribute.RigidBodyState.schema,
                  Attribute.Rotation.schema,
                  Attribute.StatEvent.schema,
                  Attribute.String.schema,
                  Attribute.TeamPaint.schema,
                  Attribute.Title.schema,
                  Attribute.UniqueId.schema,
                  Attribute.WeldedInfo.schema,
                  AttributeMapping.schema,
                  AttributeValue.schema,
                  Cache.schema,
                  ClassMapping.schema,
                  CompressedWord.schema,
                  CompressedWordVector.schema,
                  contentSchema,
                  Dictionary.elementSchema Property.schema,
                  Dictionary.schema Property.schema,
                  F32.schema,
                  Frame.schema,
                  Header.schema,
                  I32.schema,
                  I64.schema,
                  I8.schema,
                  Initialization.schema,
                  Int8Vector.schema,
                  Keyframe.schema,
                  RList.schema Attribute.Product.schema,
                  Mark.schema,
                  Message.schema,
                  ObjectTarget.schema,
                  Property.schema,
                  Property.Array.schema Property.schema,
                  Property.Byte.schema,
                  Property.Struct.schema Property.schema,
                  PropertyValue.schema Property.schema,
                  Quaternion.schema,
                  RemoteId.schema,
                  RemoteId.PlayStation.schema,
                  RemoteId.PsyNet.schema,
                  RemoteId.Switch.schema,
                  Replay.schema (Section.schema Header.schema)
                    . Section.schema
                    $ contentSchema,
                  Replication.Destroyed.schema,
                  Replication.schema,
                  Replication.Spawned.schema,
                  Replication.Updated.schema,
                  ReplicationValue.schema,
                  Rotation.schema,
                  Schema.boolean,
                  Schema.integer,
                  Schema.null,
                  Schema.number,
                  Schema.string,
                  Section.schema contentSchema,
                  Section.schema Header.schema,
                  Str.schema,
                  U32.schema,
                  U64.schema,
                  U8.schema,
                  Vector.schema
                ]
          ]
  
  getDecoder ::
    Config.Config ->
    ByteString.ByteString ->
    Either ([String], Exception.SomeException) Replay.Replay
  getDecoder config = case Config.getMode config of
    Mode.Decode ->
      Rattletrap.decodeReplayFile (Config.fast config) (Config.skipCrc config)
    Mode.Encode -> Rattletrap.decodeReplayJson
  
  getEncoder :: Config.Config -> Replay.Replay -> LazyByteString.ByteString
  getEncoder config = case Config.getMode config of
    Mode.Decode -> encodeJson config
    Mode.Encode -> Rattletrap.encodeReplayFile $ Config.fast config
  
  getInput :: String -> Config.Config -> IO ByteString.ByteString
  getInput name config = case Config.input config of
    Nothing -> do
      isTerminalDevice <- IO.hIsTerminalDevice IO.stdin
      Monad.when isTerminalDevice . IO.hPutStr IO.stderr $
        unlines
          [ "-- You did not supply any input, so Rattletrap will read from STDIN.",
            "-- If that is unexpected, try running: "
              <> FilePath.combine "." name
              <> " --help"
          ]
      ByteString.getContents
    Just fileOrUrl -> case Client.parseUrlThrow fileOrUrl of
      Nothing -> ByteString.readFile fileOrUrl
      Just request -> do
        manager <- Client.newTlsManager
        response <- Client.httpLbs request manager
        pure (LazyByteString.toStrict (Client.responseBody response))
  
  putOutput :: Config.Config -> LazyByteString.ByteString -> IO ()
  putOutput =
    maybe LazyByteString.putStr LazyByteString.writeFile . Config.output
  
  encodeJson :: (Json.ToJSON a) => Config.Config -> a -> LazyByteString.ByteString
  encodeJson = Bool.bool Json.encodePretty Json.encode . Config.compact
  
  getConfig :: [String] -> IO Config.Config
  getConfig arguments = do
    let (flags, unexpectedArguments, unknownOptions, problems) =
          Console.getOpt' Console.Permute Option.all arguments
    Monad.forM_ unexpectedArguments $ \x ->
      IO.hPutStrLn IO.stderr $ "WARNING: unexpected argument `" <> x <> "'"
    Monad.forM_ unknownOptions $
      \x -> IO.hPutStrLn IO.stderr $ "WARNING: unknown option `" <> x <> "'"
    Monad.forM_ problems $ \x -> IO.hPutStr IO.stderr $ "ERROR: " <> x
    Monad.unless (null problems) Exit.exitFailure
    either fail pure $ Monad.foldM Config.applyFlag Config.initial flags
  