-- | Rewrite expressions and modules attaching tags to numeric literals.
module Sound.SC3.RW.Tag where

import Control.Monad.Trans.State {- transformers -}
import Data.Functor.Identity {- transformers -}
import Data.Generics {- syb -}
import Language.Haskell.Exts {- haskell-src-exts -}

-- | Make 'Var' 'Exp' for 'String'.
--
-- > mk_var "tag" == Var (UnQual (Ident "tag"))
mk_var :: String -> Exp
mk_var = Var . UnQual . Ident

-- | Make 'String' 'Lit'.
--
-- > mk_str_lit "c1" == Lit (String "c1")
mk_str_lit :: String -> Exp
mk_str_lit = Lit . String

-- | Make 'Int' 'Lit'.
mk_int_lit :: Integral n => n -> Exp
mk_int_lit = Lit . Int .toInteger

-- | Make 'Frac' 'Lit'.
mk_frac_lit :: Real n => n -> Exp
mk_frac_lit = Lit . Frac . toRational

-- | Numeric literal at 'Exp' else 'Nothing'
exp_num_lit :: Exp -> Maybe Literal
exp_num_lit e =
    case e of
      Lit (Int n) -> Just (Int n)
      Lit (Frac n) -> Just (Frac n)
      _ -> Nothing

-- | Tag an 'Exp' with 'String'.
tag_exp :: String -> Exp -> Exp
tag_exp k e = (mk_var "tag" `App` mk_str_lit k) `App` e

-- | Apply /f/ at 'Exp' /e/ if it is tagged, else /g/.  If the tag is
-- within a 'Paren' then it is discarded.
at_tagged :: (String -> Exp -> Exp) -> (Exp -> Exp) -> Exp -> Exp
at_tagged f g e =
    case e of
      Paren e' -> at_tagged f (Paren . g) e'
      App (App (Var (UnQual (Ident "tag"))) (Lit (String k))) e' -> f k e'
      _ -> g e

-- | Inverse of 'tag_exp'.
--
-- > let z = mk_int_lit (0::Integer)
-- > in untag_exp (Paren (tag_exp "c1" z)) == z
untag_exp :: Exp -> Exp
untag_exp = at_tagged (\_ e -> e) id

-- | Empty source location.
nil_src_loc :: SrcLoc
nil_src_loc = SrcLoc "" 0 0

mk_span_id :: String -> [Exp] -> Exp
mk_span_id k =
    let nm = XName "span"
        cl_a = XAttr (XName "class") (mk_str_lit "numeric-literal")
        id_a = XAttr (XName "id") (mk_str_lit k)
    in XTag nil_src_loc nm [cl_a,id_a] Nothing

-- > let {e = tag_exp "c1" (mk_int_lit 0)
-- >     ;r = "<span class = \"numeric-literal\" id = \"c1\" >0</span >"}
-- > in prettyPrint (tag_to_span (Paren e)) == r
tag_to_span :: Exp -> Exp
tag_to_span =
    let f k e = mk_span_id k [e]
    in at_tagged f id

-- | Variant of 'tag_exp' that derives the the tag name using a
-- 'State' counter.
tag_exp_auto :: Exp -> State Int Exp
tag_exp_auto e = do
  i <- get
  let k = 'c' : show i
  put (i + 1)
  return (tag_exp k e)

span_exp_auto :: Exp -> State Int Exp
span_exp_auto e = do
  i <- get
  let k = 'c' : show i
  put (i + 1)
  return (mk_span_id k [e])

-- | Apply /f/ at numeric literals, else /g/.
at_num_lit :: (Exp -> t) -> (Exp -> t) -> Exp -> t
at_num_lit f g e =
    case e of
      Lit (Int _) -> f e
      Lit (Frac _) -> f e
      _ -> g e

-- | 'at_num_lit' of 'tag_exp_auto'.
tag_num_lit :: Exp -> State Int Exp
tag_num_lit = at_num_lit tag_exp_auto return

-- | 'at_num_lit' of 'tag_exp_auto'.
span_num_lit :: Exp -> State Int Exp
span_num_lit = at_num_lit span_exp_auto return

type Parser r = String -> ParseResult r
type RW t m a = t -> m a
type RW_st t a = t -> State Int a
type Tr = String -> String
type Tr_m m = String -> m String
type RW_Opt = (PPLayout,Int)

-- | Parse 'String' using 'Parser' and apply 'RW'.
apply_rw :: (Monad m,Pretty a) => RW_Opt -> Parser t -> RW t m a -> Tr_m m
apply_rw (l,w) p f s = do
  let m = defaultMode {layout = l}
      r = fromParseResult (p s)
      sty = Style {mode = PageMode,lineLength = w,ribbonsPerLine = 1}
  r' <- f r
  return (prettyPrintStyleMode sty m r')

apply_rw_pure :: Pretty a => RW_Opt -> Parser t -> (t -> a) -> Tr
apply_rw_pure o p f = runIdentity . apply_rw o p (return . f)

apply_rw_st :: Pretty a => RW_Opt -> Parser t -> RW_st t a -> Tr
apply_rw_st o p f = flip evalState 1 . apply_rw o p f

-- | Rewrite 'Exp'.
--
-- > let r = "sinOsc AR (tag \"c1\" 440) (tag \"c2\" 0) * tag \"c3\" 0.1"
-- > in exp_rw "sinOsc AR 440 0 * 0.1" == r
exp_rw :: String -> String
exp_rw =
    let f = everywhereM (mkM tag_num_lit)
    in apply_rw_st (PPNoLayout,80) parseExp f

-- | Rewrite 'Module'.
--
-- > let m = ["import Sound.SC3"
-- >         ,"o = sinOsc AR (midiCPS 65.00) 0.00"
-- >         ,"a = dbAmp (-12.00)"
-- >         ,"main = audition (out 0.00 (o * a))"]
-- > in module_rw (unlines m)
module_rw :: String -> String
module_rw =
    let f = everywhereM (mkM tag_num_lit)
    in apply_rw_st (PPNoLayout,80) parseModule f

-- | Inverse of 'exp_rw'.
exp_un_rw :: String -> String
exp_un_rw =
    let f = everywhere (mkT untag_exp)
    in apply_rw_pure (PPNoLayout,80) parseExp f

-- | 'RW_Opt' for html.  The /span/ code generates long lines...
rw_html_opt :: RW_Opt
rw_html_opt = (PPOffsideRule,640)

-- | Transform re-written form to @HTML@.
--
-- > let e = "sinOsc AR 440 0 * 0.1"
-- > in exp_rw_html (exp_rw e)
exp_rw_html :: String -> String
exp_rw_html =
    let f = everywhere' (mkT tag_to_span)
    in apply_rw_pure rw_html_opt parseExp f

-- | 'Module' variant of 'exp_rw_html'.
--
-- > let m = "o = sinOsc AR 440 0 * 0.1\nmain = audition (out 0 o)"
-- > in module_rw_html (module_rw m)
module_rw_html :: String -> String
module_rw_html =
    let f = everywhere' (mkT tag_to_span)
    in apply_rw_pure rw_html_opt parseModule f

-- > exp_html "sinOsc AR 440 0 * 0.1"
exp_html :: String -> String
exp_html =
    let f = everywhereM (mkM span_num_lit)
    in apply_rw_st rw_html_opt parseExp f

-- > module_html "o = sinOsc AR 440 0 * 0.1;main = audition (out 0 o)"
module_html :: String -> String
module_html =
    let f = everywhereM (mkM span_num_lit)
    in apply_rw_st rw_html_opt parseModule f

-- > let e = "let o = sinOsc AR 440 0 * 0.1 in audition (out 0.00 o)"
-- > in putStrLn (html_framework "/home/rohan/sw/hosc-utils" (exp_html e))
html_framework :: String -> String -> String
html_framework d m =
    unlines
    ["<!DOCTYPE html>"
    ,"<html>"
    ," <head>"
    ,"  <script src=\"" ++ d ++ "/js/json-ws.04.js\"></script>"
    ,"  <link rel=\"stylesheet\" href=\"" ++ d ++ "/css/json-ws.04.css\" />"
    ," </head>"
    ," <body>"
    ,"  <pre>"
    ,m
    ,"  </pre>"
    ,"  <p id=\"sent\">[]</p>"
    ," </body>"
    ,"</html>"]