-- | Translations between integer or string values and data types.
--
-- This (internal) module converts between the character encodings of the FIG
-- files and the primitive types of "Graphics.Fig.Syntax" ('PaperSize',
-- 'CoordinateSystem', etc.).
module Graphics.Fig.Values where

import Graphics.Fig.Syntax

import qualified Data.Map as FM
import Data.Maybe
import Data.List

----------------------------------------------------------------------
-- Begin/end string codes for objects
----------------------------------------------------------------------

begin_color = "0"
begin_ellipse = "1"
begin_polyline = "2"
begin_spline = "3"
begin_text = "4"
begin_arc = "5"
begin_compound = "6"
begin_comment = "#"

end_compound = "-6"

----------------------------------------------------------------------
-- Header elements
----------------------------------------------------------------------

header_version = "#FIG 3.2"

(toOrientation, fromOrientation) = toFromValue list
    where
    list =
        [ ("Landscape", Landscape)
        , ("Portrait", Portrait)
        ]

(toUnits, fromUnits) = toFromValue list
    where
    list =
        [ ("Metric", Metric)
        , ("Inches", Inches)
        ]

(toPaperSize, fromPaperSize) = toFromValue list
    where
    list =
        [ ("Letter", Letter)
        , ("Legal", Legal)
        , ("Ledger", Ledger)
        , ("Tabloid", Tabloid)
        , ("A", A)
        , ("B", B)
        , ("C", C)
        , ("D", D)
        , ("E", E)
        , ("A4", A4)
        , ("A3", A3)
        , ("A2", A2)
        , ("A1", A1)
        , ("A0", A0)
        , ("B5", B5)
        ]

(toMultiplePage, fromMultiplePage) = toFromValue list
    where
    list =
        [ ("Single", Single)
        , ("Multiple", Multiple)
        ]

(toTransparent, fromTransparent) = (to, from)
    where
    to (-3) = Right Background
    to (-2) = Right None
    to (-1) = Right TransparentDefault
    to n =
        either
            (const (Left ("Transparency value `" ++ show n ++ "' out of range")))
            (Right . Transparent) (toColorSpec n)

    from Background = -3
    from None = -2
    from TransparentDefault = -1
    from (Transparent spec) = fromColorSpec spec

(toCoordinateSystem, fromCoordinateSystem) = toFromValue list
    where
    list = [(1, LowerLeft), (2, UpperLeft)]

----------------------------------------------------------------------
-- Fonts
----------------------------------------------------------------------

(toFontFlags, fromFontFlags) = (to, from)
    where
    to n
        | n < 0 || n > 15 = Left ("Font flag `" ++ show n ++ "' out of range")
        | otherwise = Right (FontFlags b3 b1 b0, b2)
        where
        [b0, b1, b2, b3] =
            let xs = toBitList n in xs ++ replicate (4 - length xs) False

    from ps (FontFlags b3 b1 b0) = fromBitList [b0, b1, b2, b3]
        where
        b2 = case ps of Ps _ -> True ; Latex _ -> False

    fromBitList bs =
        foldr
            (\x acc -> (if x then 1 else 0) + 2 * acc)
            0
        bs

    toBitList n = unfoldr f n
        where
        f 0 = Nothing
        f n = let (rest, bit) = divMod n 2 in Just (bit == 1, rest)

(toFont, fromFont) = (to, from)
    where
    to ps n =
        if ps
            then either Left (Right . Ps) (toPsFont n)
            else either Left (Right . Latex) (toLatexFont n)

    from (Ps font) = fromPsFont font
    from (Latex font) = fromLatexFont font

(toPsFont, fromPsFont) = toFromValue list
    where
    list =
        [ (-1, PsDefault)
        , (0, TimesRoman)
        , (1, TimesItalic)
        , (2, TimesBold)
        , (3, TimesBoldItalic)
        , (4, AvantGardeBook)
        , (5, AvantGardeBookOblique)
        , (6, AvantGardeDemi)
        , (7, AvantGardeDemiOblique)
        , (8, BookmanLight)
        , (9, BookmanLightItalic)
        , (10, BookmanDemi)
        , (11, BookmanDemiItalic)
        , (12, Courier)
        , (13, CourierOblique)
        , (14, CourierBold)
        , (15, CourierBoldOblique)
        , (16, Helvetica)
        , (17, HelveticaOblique)
        , (18, HelveticaBold)
        , (19, HelveticaBoldOblique)
        , (20, HelveticaNarrow)
        , (21, HelveticaNarrowOblique)
        , (22, HelveticaNarrowBold)
        , (23, HelveticaNarrowBoldOblique)
        , (24, NewCenturySchoolbookRoman)
        , (25, NewCenturySchoolbookItalic)
        , (26, NewCenturySchoolbookBold)
        , (27, NewCenturySchoolbookBoldItalic)
        , (28, PalatinoRoman)
        , (29, PalatinoItalic)
        , (30, PalatinoBold)
        , (31, PalatinoBoldItalic)
        , (32, Symbol)
        , (33, ZapfChanceryMediumItalic)
        , (34, ZapfDingbats)
        ]

(toLatexFont, fromLatexFont) = toFromValue list
    where
    list =
        [ (0, LatexDefault)
        , (1, Roman)
        , (2, Bold)
        , (3, Italic)
        , (4, SansSerif)
        , (5, Typewriter)
        ]

----------------------------------------------------------------------
-- Pictures
----------------------------------------------------------------------

(toFlipped, fromFlipped) = toFromValue list
    where
    list = [(0, Normal), (1, Flipped)]

----------------------------------------------------------------------
-- Arrows
----------------------------------------------------------------------

(toArrowStyle, fromArrowStyle) = toFromValue list
    where
    list = [(0, HollowArrow), (1, FilledArrow)]

(toArrowType, fromArrowType) = toFromValue list
    where
    list = [(0, Stick), (1, Closed), (2, Indented), (3, Pointed)]

----------------------------------------------------------------------
-- Lines styles
----------------------------------------------------------------------

(toCapStyle, fromCapStyle) = toFromValue list
    where
    list = [(0, Butt), (1, CapRound), (2, Projecting)]

(toJoinStyle, fromJoinStyle) = toFromValue list
    where
    list = [(0, Miter), (1, JoinRound), (2, Bevel)]

(toLineStyle, fromLineStyle) = toFromValue list
    where
    list =
        [ (-1, LineStyleDefault)
        , (0, Solid)
        , (1, Dashed)
        , (2, Dotted)
        , (3, DashDotted)
        , (4, DashDoubleDotted)
        , (5, DashTripleDotted)
        ]

----------------------------------------------------------------------
-- Pen and fill colors
----------------------------------------------------------------------

(toColorSpec, fromColorSpec) = (to, from)
    where
    to n
        | -1 <= n && n <= 31 = toColorSpec' n
        | 32 <= n && n <= 543 = Right (UserDefined n)
        | otherwise = Left ("Color value `" ++ show n ++ "' out of range")

    from (UserDefined n) = n
    from spec = fromColorSpec' spec

    (toColorSpec', fromColorSpec') = toFromValue list
        where
        list =
            [ (-1, ColorSpecDefault)
            , (0, Black)
            , (1, Blue)
            , (2, Green)
            , (3, Cyan)
            , (4, Red)
            , (5, Magenta)
            , (6, Yellow)
            , (7, White)
            , (8, Blue4)
            , (9, Blue3)
            , (10, Blue2)
            , (11, LtBlue)
            , (12, Green4)
            , (13, Green3)
            , (14, Green2)
            , (15, Cyan4)
            , (16, Cyan3)
            , (17, Cyan2)
            , (18, Red4)
            , (19, Red3)
            , (20, Red2)
            , (21, Magenta4)
            , (22, Magenta3)
            , (23, Magenta2)
            , (24, Brown4)
            , (25, Brown3)
            , (26, Brown2)
            , (27, Pink4)
            , (28, Pink3)
            , (29, Pink2)
            , (30, Pink)
            , (31, Gold)
            ]

----------------------------------------------------------------------
-- Area fill
----------------------------------------------------------------------

(toAreaFill, fromAreaFill) = (to, from)
    where
    to n
        | n == -1 = Right NoFill
        | 0 <= n && n <= 40 = Right (Filled (n * 5))
        | 41 <= n && n <= 62 = Right (Pattern (n - 41))
        | otherwise = Left ("Area fill value `" ++ show n ++ "' out of range")

    from NoFill = -1
    from (Filled n) = n `div` 5
    from (Pattern n) = n + 41

----------------------------------------------------------------------
-- Utilities
----------------------------------------------------------------------

toFromValue list = (toValue, fromValue)
    where
    toValue x =
        case FM.lookup x toFM of
            Nothing -> Left (error x)
            Just y -> Right y

    fromValue x = fromJust (FM.lookup x fromFM)

    toFM = FM.fromList list

    fromFM = FM.fromList (map inv list)

    inv (x, y) = (y, x)

    error x =
        "expecting one of " ++
        concat
            (intersperse " "
                (map show (FM.keys toFM))) ++ "\n" ++
        "got " ++ show x