{-|
  PriLabels are labels with a priority.

  PriLabels are usefull when widgets can have their label set multiple
  times. This happens with genericcally created widgets. For example in:

  data Foo = Foo { someName :: Bar }
  data Bar = Bar Int

  widgets created from Bar instances can have labels set due it's
  constructor name and the fieldName in Foo (someName). A GUI
  application programmer may also set the widgets label.

  When a widget has set it's label multiple times, the priority can be
  used to decide which label should be chosen.
-}
module Graphics.UI.SybWidget.PriLabel
    ( PriLabel(..)
    , Priority(..)
    , badConstrLabel, goodConstrLabel, fieldNameLabel, userDefinedLabel
    , bestLabel, humanizeLabel
    , defaultLabel, labelless
    )
where

import Char

-- |Prioritized label. If two 'PriLabel' can be used for some
--  component, then the one with highest priority is used.
data PriLabel = PriLabel { priority :: Priority, labelString :: String } deriving (Show, Eq)
-- |The label priority.
data Priority = BadConstr | GoodConstr | FieldName | UserDefined deriving (Show, Ord, Eq, Bounded, Enum)

badConstrLabel, goodConstrLabel, fieldNameLabel, userDefinedLabel :: String -> PriLabel
badConstrLabel   label = PriLabel BadConstr   label
goodConstrLabel  label = PriLabel GoodConstr  label
fieldNameLabel   label = PriLabel FieldName   label
userDefinedLabel label = PriLabel UserDefined label

-- |Creates a default (lowest priority) PriLabel
defaultLabel :: String -> PriLabel
defaultLabel label = PriLabel BadConstr label

labelless :: PriLabel
labelless = defaultLabel ""

-- |Choose label with highest priority. If equal then choose the left
-- |(first parameter) label.
bestLabel :: PriLabel -> PriLabel -> PriLabel
bestLabel left@(PriLabel priL _) right@(PriLabel priR _)
    | priL >= priR  = left
    | otherwise     = right

-- |Humanized label strings, by turning labels like "someLabelName"
-- into "Some label name".
humanizeLabel :: PriLabel -> PriLabel
humanizeLabel (PriLabel pri label) = PriLabel pri label'
    where
    label' | pri == UserDefined || not (and (map isLegitChar label))
               -- safegaurd against accidentally calling humanizeLabel twice 
               -- and against parsing special labels like "()"
                                = label
           | elem '_' label     = (first toUpper . nonCamel) label
           | otherwise          = camelCase label
    -- humanizing non-camel case identifiers
    isLegitChar x = isAlphaNum x || elem x ['_', '\'']
    nonCamel []             = []
    nonCamel ('_':[])       = []
    nonCamel (x:[])         = x:[]
    nonCamel ('_':x:[])     = ' ':toUpper x:[]
    nonCamel (x:y:[])       = x:nonCamel [y]
    nonCamel ('_':x:y:xs)
        | isUpper y         = ' ':toUpper x:nonCamel (y:xs)
        | otherwise         = ' ':toLower x:nonCamel (y:xs)
    nonCamel (x:xs)         = x:nonCamel xs
    --
    camelCase []            = []
    camelCase (x:xs)        = toUpper x : (seperateWords [] xs)
    seperateWords cs []           = cs
    seperateWords [] (x:xs)
        | isUpper x               = ' ':seperateWords [x] xs
        | otherwise               =   x:seperateWords [] xs
    seperateWords (c:[]) (x:xs)
        | isUpper x               = seperateWords (c:x:[]) xs
        | otherwise               = toLower c:x:seperateWords [] (xs)
    seperateWords (cs) (x:xs)
        | isUpper x               = seperateWords (cs ++ [x]) xs
        | otherwise               = (init cs) ++ ' ':(toLower $ last cs):x:seperateWords [] xs
    first _ []     = []
    first f (x:xs) = (f x):xs