-- | Rendering 'Shape's to simple textual descriptions.
--
-- This module imports "Geometry", so the hyperlinked-source view has
-- cross-module links to follow.
module Geometry.Render
  ( -- * Descriptions
    describe
  , describeAll

    -- * Styling
  , Style (..)
  , defaultStyle
  ) where

import Geometry (Measurable (..), Shape (..))

-- | How to render a description.
data Style = Style
  { Style -> String
styleUnit :: String
  -- ^ unit suffix appended to measurements, e.g. @\"cm\"@
  , Style -> Int
stylePrecision :: Int
  -- ^ number of decimal places
  }
  deriving (Style -> Style -> Bool
(Style -> Style -> Bool) -> (Style -> Style -> Bool) -> Eq Style
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Style -> Style -> Bool
== :: Style -> Style -> Bool
$c/= :: Style -> Style -> Bool
/= :: Style -> Style -> Bool
Eq, Int -> Style -> ShowS
[Style] -> ShowS
Style -> String
(Int -> Style -> ShowS)
-> (Style -> String) -> ([Style] -> ShowS) -> Show Style
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Style -> ShowS
showsPrec :: Int -> Style -> ShowS
$cshow :: Style -> String
show :: Style -> String
$cshowList :: [Style] -> ShowS
showList :: [Style] -> ShowS
Show)

-- | A reasonable default: centimetres, two decimals.
defaultStyle :: Style
defaultStyle :: Style
defaultStyle = Style {styleUnit :: String
styleUnit = String
"cm", stylePrecision :: Int
stylePrecision = Int
2}

-- | Describe a single shape's area and perimeter.
describe :: Style -> Shape -> String
describe :: Style -> Shape -> String
describe Style
style Shape
shape =
  Shape -> String
kind Shape
shape
    String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
": area "
    String -> ShowS
forall a. [a] -> [a] -> [a]
++ Double -> String
render (Shape -> Double
forall a. Measurable a => a -> Double
area Shape
shape)
    String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
", perimeter "
    String -> ShowS
forall a. [a] -> [a] -> [a]
++ Double -> String
render (Shape -> Double
forall a. Measurable a => a -> Double
perimeter Shape
shape)
  where
    kind :: Shape -> String
kind (Circle Point
_ Double
_) = String
"circle"
    kind (Rectangle Point
_ Point
_) = String
"rectangle"
    render :: Double -> String
render Double
x = Double -> String
forall a. Show a => a -> String
show (Int -> Double -> Double
roundTo (Style -> Int
stylePrecision Style
style) Double
x) String -> ShowS
forall a. [a] -> [a] -> [a]
++ Style -> String
styleUnit Style
style

-- | Describe many shapes, one per line.
describeAll :: Style -> [Shape] -> String
describeAll :: Style -> [Shape] -> String
describeAll Style
style = [String] -> String
unlines ([String] -> String) -> ([Shape] -> [String]) -> [Shape] -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Shape -> String) -> [Shape] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Style -> Shape -> String
describe Style
style)

roundTo :: Int -> Double -> Double
roundTo :: Int -> Double -> Double
roundTo Int
n Double
x = Integer -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Double -> Integer
forall b. Integral b => Double -> b
forall a b. (RealFrac a, Integral b) => a -> b
round (Double
x Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
f) :: Integer) Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Double
f
  where
    f :: Double
f = Double
10 Double -> Int -> Double
forall a b. (Fractional a, Integral b) => a -> b -> a
^^ Int
n