{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -Wno-missing-import-lists #-}

{- ORMOLU_DISABLE -}

-- | This module serves as an alternative entry point to "Data.Bytes", for
-- when tracking uploaded vs. downloaded bytes is necessary. It provides the
-- types and operations for typical usage and is usually the only import
-- required. The core concept is:
--
-- 1. Wrapping a numeric value representing bytes in a new type.
-- 2. Attaching phantom labels representing the units (e.g. B, K, M, ...).
-- 3. Attaching phantom labels representing the direction (i.e. Down, Up).
--
-- This prevents mistakes, such as adding two different byte sizes/directions,
-- or converting between sizes incorrectly.
--
-- @since 0.1
module Data.Bytes.Network
  ( -- * Introduction
    -- $intro
    NetBytes (..),
    Size (..),
    Direction (..),

    -- * Basic Usage

    -- ** Construction
    -- $construction

    -- ** Unknown Size
    -- $size1
    SomeNetSize,

    -- *** Sized
    -- $size2
    Sized (..),

    -- *** Optics
    -- $size3

    -- ** Unknown Direction
    -- $direction1
    SomeNetDir,
    SomeNet,
    -- $direction2
    Directed (..),
    -- $direction3

    -- ** Elimination

    -- *** RawNumeric
    -- $elimination1
    RawNumeric (..),

    -- *** HasField
    -- $elimination2

    -- *** Optics
    -- $elimination3

    -- * Transformations

    -- ** Converting Units
    Conversion (..),

#if MIN_VERSION_base(4, 20, 0)
    Conv.convert,
#endif

    -- ** Normalization
    Normalize (..),

    -- * Algebra
    -- $algebra
    module Numeric.Algebra,
    module Numeric.Literal.Integer,
    module Numeric.Literal.Rational,

    -- * Text

    -- ** Pretty Printing
    -- $pretty
    module Data.Bytes.Formatting,

    -- ** Parsing
    Parser,
    parse,

    -- * Optics

    -- ** Core
    _MkNetBytes,
    _MkSomeNetSize,
    _MkSomeNetDir,
    _MkSomeNet,

    -- ** Size
    _B,
    _K,
    _M,
    _G,
    _T,
    _P,
    _E,
    _Z,
    _Y,

    -- ** Direction
    _Down,
    _Up,

    -- * Reexports
    Default (def),
  )
where

{- ORMOLU_ENABLE -}

import Data.Bytes.Class.Conversion (Conversion (Converted, convert_))
#if MIN_VERSION_base(4, 20, 0)
import Data.Bytes.Class.Conversion qualified as Conv
#endif
import Data.Bytes.Class.Normalize (Normalize (Norm, normalize))
import Data.Bytes.Class.Parser (Parser, parse)
import Data.Bytes.Class.RawNumeric (RawNumeric (Raw, toRaw))
import Data.Bytes.Formatting
import Data.Bytes.Network.Direction
  ( Directed (HideDirection, directionOf, hideDirection),
    Direction (Down, Up),
    _Down,
    _Up,
  )
import Data.Bytes.Network.Internal
  ( NetBytes (MkNetBytes, MkNetBytesP),
    SomeNet,
    SomeNetDir,
    SomeNetSize,
    _MkNetBytes,
    _MkSomeNet,
    _MkSomeNetDir,
    _MkSomeNetSize,
  )
import Data.Bytes.Size
  ( Size (B, E, G, K, M, P, T, Y, Z),
    Sized (HideSize, hideSize, sizeOf),
    _B,
    _E,
    _G,
    _K,
    _M,
    _P,
    _T,
    _Y,
    _Z,
  )
import Numeric.Algebra
import Numeric.Literal.Integer
import Numeric.Literal.Rational

-- $intro
-- The main idea is to attach phantom labels to the numeric bytes, so we
-- can track the size and direction units. This allows us to safely
-- manipulate byte values without mixing up units, performing incorrect
-- conversions, etc.
--
-- The core types are a newtype wrapper 'NetBytes', the 'Size' units, and the
-- 'Direction' units:

-- $construction
-- There are several ways to construct a 'NetBytes' type.
--
-- 1. 'FromInteger'
--
--     >>> afromInteger 80 :: NetBytes Up M Int
--     MkNetBytes (MkBytes 80)
--
-- 2. Directly
--
--     >>> import Data.Bytes (Bytes (MkBytes))
--     >>> MkNetBytes (MkBytes 80) :: NetBytes Down M Int
--     MkNetBytes (MkBytes 80)
--
--     >>> -- using the @MkNetBytesP :: n -> NetBytes d s n@ pattern synonym
--     >>> MkNetBytesP 80 :: NetBytes Up K Int
--     MkNetBytes (MkBytes 80)
--
-- 3. Optics (@optics-core@)
--
--     >>> -- 1. Using _MkNetBytes to construct a NetBytes from a Bytes
--     >>> import Optics.Core (review, (%))
--     >>> import Data.Bytes (_MkBytes)
--     >>> (review _MkNetBytes (MkBytes 80)) :: NetBytes Up G Int -- Bytes -> NetBytes
--     MkNetBytes (MkBytes 80)
--
--     >>> -- 2. Using _MkNetBytes and _MkBytes to construct a NetBytes from a
--     >>> --    numeric value.
--     >>> (review (_MkNetBytes % _MkBytes) 70) :: NetBytes Up G Int -- n -> NetBytes
--     MkNetBytes (MkBytes 70)
--
--     >>> -- 3. Using #unNetBytes to construct a NetBytes from a numeric value.
--     >>> (review #unNetBytes 70) :: NetBytes Up G Int -- n -> NetBytes
--     MkNetBytes (MkBytes 70)

-- $size1
-- We sometimes have to deal with unknown sizes at runtime, which presents
-- a problem. We handle this with the @'SomeNetSize'@ type, which existentially
-- quantifies the 'Size':

-- $size2
-- Fortunately, we do not have to directly use the constructor or singletons.
-- We can instead use the 'Sized' class.

-- $size3
-- Once again, we can use optics for this.
--
-- >>> import Optics.Core (review)
-- >>> review _MkSomeNetSize (MkNetBytesP 70 :: NetBytes Down G Int)
-- MkSomeNetSize SG (MkNetBytes (MkBytes 70))

-- $direction1
-- Like sizes, we can also handle unknown directions at runtime. The types
-- involved here are 'SomeNetDir' (hiding direction only) and 'SomeNet'
-- (hiding both direction and size).

-- $direction2
-- Analogous to the 'Sized' class, we have 'Directed'.

-- $direction3
-- Once again, we can use optics for this.
--
-- >>> import Optics.Core (review)
-- >>> let x = MkNetBytesP 70 :: NetBytes Down G Int
-- >>> review _MkSomeNetDir x
-- MkSomeNetDir SDown (MkNetBytes (MkBytes 70))
--
-- >>> review _MkSomeNet x
-- MkSomeNet SDown SG (MkNetBytes (MkBytes 70))

-- $elimination1
-- We provide the 'RawNumeric' class for conveniently unwrapping a type
-- to the underlying numeric value.

-- $elimination2
-- We can use 'GHC.Records.HasField' for this too.
--
-- >>> -- {-# LANGUAGE OverloadedRecordDot #-}
-- >>> let x1 = MkNetBytesP 7 :: NetBytes Up G Int
-- >>> x1.unNetBytes
-- 7
--
-- >>> let x2 = hideSize x1 :: SomeNetSize Up Int
-- >>> x2.unSomeNetSize
-- 7
--
-- >>> let x3 = (hideDirection x1) :: SomeNetDir G Int
-- >>> x3.unSomeNetDir
-- 7
--
-- >>> let x4 = (hideDirection x2) :: SomeNet Int
-- >>> x4.unSomeNet
-- 7

-- $elimination3
-- Optics are another option. The underscore-prefixed optics unwrap one level
-- at a time, since we can freely compose them.
--
-- >>> import Optics.Core (view, (%))
-- >>> import Data.Bytes (_MkBytes)
-- >>> let x1 = MkNetBytesP 7 :: NetBytes Up G Int
-- >>> view (_MkNetBytes % _MkBytes) x1
-- 7
--
-- >>> -- notice we have to convert the numeric value since the requested
-- >>> -- return type ('M') differs from the original ('G')
-- >>> let x2 = hideSize x1 :: SomeNetSize Up Int
-- >>> (view _MkSomeNetSize x2) :: NetBytes Up M Int
-- MkNetBytes (MkBytes 7000)
--
-- >>> view (_MkSomeNetSize % (_MkNetBytes @M) % _MkBytes) x2
-- 7000
--
-- The @-XOverloadedLabel@ instances unwrap all the way to the underlying numeric
-- value.
--
-- >>> view #unNetBytes x1
-- 7
--
-- >>> view #unSomeNetSize x2
-- 7
--
-- >>> let x3 = hideDirection x1 :: SomeNetDir G Int
-- >>> view #unSomeNetDir x3
-- 7
--
-- >>> let x4 = hideSize x3 :: SomeNet Int
-- >>> view #unSomeNet x4
-- 7

-- $pretty
-- We provide several formatters for pretty-printing different byte types.
--
-- >>> import Data.Default (Default (def))
-- >>> let bf = MkFloatingFormatter (Just 2)
-- >>> let b = MkNetBytesP @Up @G @Float 203.301
-- >>> formatSizedDirected bf def def b
-- "203.30 gb up"

-- $algebra
--
-- The built-in 'Num' class is abandoned in favor of
-- [algebra-simple](https://github.com/tbidne/algebra-simple/)'s
-- algebraic hierarchy based on abstract algebra. This is motivated by a
-- desire to:
--
-- 1. Provide a consistent API.
-- 2. Avoid 'Num'\'s infelicities (e.g. nonsense multiplication,
--    dangerous 'fromInteger').
--
-- 'NetBytes' and 'SomeNetSize' are both
-- 'Numeric.Algebra.Additive.AGroup.AGroup's. A 'Numeric.Algebra.Ring.Ring'
-- instance is not provided because multiplication is nonsensical:
--
-- \[
-- x \;\textrm{mb} \times y \;\textrm{mb} = xy \;\textrm{mb}^2.
-- \]
--
-- Fortunately, multiplying bytes by some kind of scalar is both useful /and/
-- has an easy interpretation: 'NetBytes' forms a
-- 'Numeric.Algebra.Module.Module' over a 'Numeric.Algebra.Ring.Ring'
-- (resp. 'Numeric.Algebra.VectorSpace.VectorSpace' over a
-- 'Numeric.Algebra.Field.Field'). This allows us to multiply a 'NetBytes' or
-- 'SomeNetSize' by a scalar in a manner consistent with the above API.
--
-- == Examples
-- === Addition/Subtraction
-- >>> import Numeric.Algebra (ASemigroup ((.+.)), AGroup ((.-.)))
-- >>> let mb1 = MkNetBytesP 20 :: NetBytes 'Down 'M Int
-- >>> let mb2 = MkNetBytesP 50 :: NetBytes 'Down 'M Int
-- >>> mb1 .+. mb2
-- MkNetBytes (MkBytes 70)
-- >>> mb1 .-. mb2
-- MkNetBytes (MkBytes (-30))
--
-- >>> let kb = MkNetBytesP 50 :: NetBytes 'Down 'K Int
-- >>> -- mb1 .+. kb -- This would be a type error
--
-- >>> let mbUp = MkNetBytesP 50 :: NetBytes 'Up 'M Int
-- >>> -- mb1 .+. mbUp -- This would be a type error
--
-- === Multiplication
-- >>> import Numeric.Algebra (MSemiSpace ((.*)))
-- >>> mb1 .* 10
-- MkNetBytes (MkBytes 200)
--
-- === Division
-- >>> import Numeric.Algebra (MSpace ((.%)))
-- >>> mb1 .% 10
-- MkNetBytes (MkBytes 2)
--
-- One may wonder how the 'Numeric.Algebra.Additive.AGroup.AGroup' instance
-- for 'SomeNetSize' could possibly work. It is possible (indeed, expected)
-- that we could have two 'SomeNetSize's that have different underlying
-- 'NetBytes' types. To handle this, the 'SomeNetSize' instance will convert
-- both 'NetBytes' to a 'NetBytes' ''B' before adding/subtracting.
--
-- >>> let some1 = hideSize (MkNetBytesP 1000 :: NetBytes 'Down 'G Double)
-- >>> let some2 = hideSize (MkNetBytesP 500_000 :: NetBytes 'Down 'M Double)
-- >>> some1 .+. some2
-- MkSomeNetSize SB (MkNetBytes (MkBytes 1.5e12))
-- >>> some1 .-. some2
-- MkSomeNetSize SB (MkNetBytes (MkBytes 5.0e11))
--
-- This respects 'SomeNetSize'\'s equivalence-class base 'Eq'.