{-# LANGUAGE TemplateHaskell, DerivingVia #-}
-- | Project names for software projects.
module Drasil.System.ProjectName (
  ProjectName,
  mkProjectName, mkCommonProjName,
  HasProjectName(..),
  projTitleS, projAbrvS, introduceProjName
) where

import Control.Lens ((^.), makeLensesFor, makeClassyFor)
import Data.Char (toLower, isAlphaNum, isPrint)
import Data.List.Extras (replaceAll)

import Drasil.Database (UID, HasUID(..), declareHasChunkRefs, Generically(..))
import Language.Drasil (NP, Sentence(Ch), SentenceStyle(..), TermCapitalization(CapW), sParen, (+:+))

-- | General software projects generally have at least 3 names: a human-readable
-- title, a human-preferred abbreviation, and a preferred repository name
-- (slug). For example, "Drasil: A Software Generation Framework", "Drasil", and
-- "drasil".
data ProjectName = PN
  { -- | The UID.
    ProjectName -> UID
_pnUID        :: UID,
    -- | Human-readable project title.
    ProjectName -> NP
_title        :: NP,
    -- | Preferred human-readable project abbreviation (the identifier commonly
    -- used to refer to the project, likely abbreviated).
    ProjectName -> String
_abbreviation :: String,
    -- | Preferred project repository name (slug).
    ProjectName -> String
_repo         :: String
  }
declareHasChunkRefs ''ProjectName
makeLensesFor [("_pnUID", "pnUID")] ''ProjectName
makeClassyFor "HasProjectName" "projectName" [("_title", "projTitle"), ("_abbreviation", "projAbrv"), ("_repo", "projRepoName")] ''ProjectName

instance HasUID ProjectName where
  uid :: Getter ProjectName UID
uid = (UID -> f UID) -> ProjectName -> f ProjectName
Lens' ProjectName UID
pnUID

-- | Construct a 'ProjectName' from a 'UID', title ('NP'), abbreviation ('String'),
-- and repository name formatter ('String -> String'). Errors if the repository
-- name contains characters other than alphanumeric characters or @'-'@.
--
-- Abbreviation rules:
-- 1. Non-empty, maximum of 64 chars.
-- 2. Only printable characters.
--
-- Repo name rules:
-- 1. Non-empty, maximum of 64 chars.
-- 2. Lowercase alphanumeric or '-' only.
mkProjectName :: UID -> NP -> String -> (String -> String) -> ProjectName
mkProjectName :: UID -> NP -> String -> (String -> String) -> ProjectName
mkProjectName UID
u NP
title String
abrv String -> String
abrv2repoF
  | Bool -> Bool
not ((Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isPrint String
abrv)                         = String -> ProjectName
forall a. HasCallStack => String -> a
error String
"Project abbreviation may only contain printable characters."
  | String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
abrv Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
64 Bool -> Bool -> Bool
|| String -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
abrv                  = String -> ProjectName
forall a. HasCallStack => String -> a
error String
"Project abbreviation must be between [1,64] characters long."
  | (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (\Char
c -> Bool -> Bool
not (Char -> Bool
isAlphaNum Char
c) Bool -> Bool -> Bool
&& Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'-') String
rpo = String -> ProjectName
forall a. HasCallStack => String -> a
error String
"Project repository name must be alphanumeric."
  | String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
rpo Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
64 Bool -> Bool -> Bool
|| String -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
rpo                    = String -> ProjectName
forall a. HasCallStack => String -> a
error String
"Project repository name must be between [1,64] characters long."
  | Bool
otherwise          = UID -> NP -> String -> String -> ProjectName
PN UID
u NP
title String
abrv String
rpo
  where
    rpo :: String
rpo = String -> String
abrv2repoF String
abrv

-- | Construct a 'ProjectName' using a common repository name formatting rule
-- (lowercase, naively replace non-alphanumeric characters with @'-'@s).
mkCommonProjName :: UID -> NP -> String -> ProjectName
mkCommonProjName :: UID -> NP -> String -> ProjectName
mkCommonProjName UID
u NP
ttl String
ab = UID -> NP -> String -> (String -> String) -> ProjectName
mkProjectName UID
u NP
ttl String
ab String -> String
frmtr
  where
    frmtr :: String -> String
frmtr = (Char -> Char) -> String -> String
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toLower (String -> String) -> (String -> String) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> Char -> String -> String
forall a. (a -> Bool) -> a -> [a] -> [a]
replaceAll (Bool -> Bool
not (Bool -> Bool) -> (Char -> Bool) -> Char -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> Bool
isAlphaNum) Char
'-'

-- | Get the title of a 'ProjectName' (as a 'Sentence').
projTitleS :: ProjectName -> Sentence
projTitleS :: ProjectName -> Sentence
projTitleS = SentenceStyle -> TermCapitalization -> UID -> Sentence
Ch SentenceStyle
TermStyle TermCapitalization
CapW (UID -> Sentence)
-> (ProjectName -> UID) -> ProjectName -> Sentence
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (ProjectName -> Getting UID ProjectName UID -> UID
forall s a. s -> Getting a s a -> a
^. Getting UID ProjectName UID
forall c. HasUID c => Getter c UID
Getter ProjectName UID
uid)

-- | Get the human-readable abbreviation of a 'ProjectName' (as a 'Sentence').
projAbrvS :: ProjectName -> Sentence
projAbrvS :: ProjectName -> Sentence
projAbrvS = SentenceStyle -> TermCapitalization -> UID -> Sentence
Ch SentenceStyle
ShortStyle TermCapitalization
CapW (UID -> Sentence)
-> (ProjectName -> UID) -> ProjectName -> Sentence
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (ProjectName -> Getting UID ProjectName UID -> UID
forall s a. s -> Getting a s a -> a
^. Getting UID ProjectName UID
forall c. HasUID c => Getter c UID
Getter ProjectName UID
uid)

-- | Combine a 'ProjectName''s title and abbreviation into an introductory
-- 'Sentence' (e.g. "Title (Abbreviation)").
introduceProjName :: ProjectName -> Sentence
introduceProjName :: ProjectName -> Sentence
introduceProjName ProjectName
proj = ProjectName -> Sentence
projTitleS ProjectName
proj Sentence -> Sentence -> Sentence
+:+ Sentence -> Sentence
sParen (ProjectName -> Sentence
projAbrvS ProjectName
proj)