module Database.Design.Ampersand.FSpec.FPA (FPA(..), FP(..), FPType(..), ShowLang(..), fpAnalyze, fpVal, fpaPlugInfo, fpaInterface) where 
                                           -- fpaPlugInfo and fpaInterface are exported for legacy modules Statistics and FSpec2Excel

import Database.Design.Ampersand.Misc (Lang(..))
import Database.Design.Ampersand.ADL1
import Database.Design.Ampersand.Classes
import Database.Design.Ampersand.FSpec.FSpec
import Database.Design.Ampersand.Basics
import Data.Maybe

--fatal :: Int -> String -> a
--fatal = fatalMsg "FSpec.FPA"

data FPA = FPA { dataModelFPA :: ([FP], Int), userTransactionFPA :: ([FP],Int) } deriving Show

-- NOTE: FPA constructors are in Dutch (so 'output function' is not OF, but UF)   

data Complexity = Eenvoudig | Gemiddeld | Moeilijk deriving (Show, Eq, Ord)

data FPType
 = ILGV -- ^ bevat permanente, voor de gebruiker relevante gegevens. De gegevens worden door het systeem gebruikt en onderhouden. Onder "onderhouden" verstaat FPA het toevoegen, wijzigen of verwijderen van gegevens.
 | KGV  -- ^ bevat permanente, voor de gebruiker relevante gegevens. Deze gegevens worden door het systeem gebruikt, maar worden door een ander systeem onderhouden (voor dat andere systeem is het dus een ILGV).
 | IF   -- ^ verwerkt gegevens in een ILGV van het systeem. (dus create, update en delete functies)
 | UF   -- ^ presenteert gegevens uit het systeem. Voorbeelden: het afdrukken van alle debiteuren; het aanmaken van facturen; het aanmaken van een diskette met betalingsopdrachten; het medium is hierbij niet van belang: papier, scherm, magneetband, datacom, enzovoorts.
 | OF   -- ^ is een speciaal (eenvoudig) soort uitvoerfunctie. Een opvraagfunctie presenteert gegevens uit het systeem op basis van een uniek identificerend zoekgegeven, waarbij geen aanvullende bewerkingen (zoals berekeningen of het bijwerken van een gegevensverzameling) plaats hebben. Voorbeeld: Het tonen van de gegevens van de klant met klantnummer 123456789.
  deriving Show

data FP = FP { fpType :: FPType, fpName :: String, fpComplexity :: Complexity } deriving Show


-- | Valuing of function points according to par. 3.9 (UK) or par. 2.9 (NL), see http://www.nesma.nl/sectie/fpa/hoefpa.asp
fpVal :: FP -> Int
fpVal FP{fpType=ILGV, fpComplexity=Eenvoudig} = 7
fpVal FP{fpType=ILGV, fpComplexity=Gemiddeld} = 10
fpVal FP{fpType=ILGV, fpComplexity=Moeilijk}  = 15
fpVal FP{fpType=KGV,  fpComplexity=Eenvoudig} = 5
fpVal FP{fpType=KGV,  fpComplexity=Gemiddeld} = 7
fpVal FP{fpType=KGV,  fpComplexity=Moeilijk}  = 10
fpVal FP{fpType=IF,   fpComplexity=Eenvoudig} = 3
fpVal FP{fpType=IF,   fpComplexity=Gemiddeld} = 4
fpVal FP{fpType=IF,   fpComplexity=Moeilijk}  = 6
fpVal FP{fpType=UF,   fpComplexity=Eenvoudig} = 4
fpVal FP{fpType=UF,   fpComplexity=Gemiddeld} = 5
fpVal FP{fpType=UF,   fpComplexity=Moeilijk}  = 7
fpVal FP{fpType=OF,   fpComplexity=Eenvoudig} = 3
fpVal FP{fpType=OF,   fpComplexity=Gemiddeld} = 4
fpVal FP{fpType=OF,   fpComplexity=Moeilijk}  = 6

fpAnalyze :: FSpec -> FPA
fpAnalyze fSpec = FPA (countFPs $ fpaDataModel fSpec) (countFPs $ fpaUserTransactions fSpec)
  where countFPs :: [FP] -> ([FP], Int)
        countFPs fps = (fps, sum $ map fpVal fps)

-- Na overleg met Frank Vogelenzang zijn de volgende criteria voor functiepuntentelling toegepast
fpaDataModel :: FSpec -> [FP]
fpaDataModel fSpec = mapMaybe fpaPlugInfo $ plugInfos fSpec

fpaPlugInfo :: PlugInfo -> Maybe FP
fpaPlugInfo p@(InternalPlug (TblSQL{fields=flds})) | Just cmplxty <- ilgvComplexity $ length flds =
  Just $ FP ILGV (name p) cmplxty
  where ilgvComplexity :: Int -> Maybe Complexity
        ilgvComplexity n | n <= 2    = Nothing 
                         | n <= 15   = Just Eenvoudig
                         | n <= 40   = Just Gemiddeld
                         | otherwise = Just Moeilijk
fpaPlugInfo _ = Nothing

fpaUserTransactions :: FSpec -> [FP]
fpaUserTransactions fSpec = map fpaInterface $ interfaceS fSpec

fpaInterface :: Interface -> FP
fpaInterface ifc = 
   let nm = name ifc
       cmplxty = depth2Cmplxty $ getDepth $ ifcObj ifc
       tp = case ifc of
              Ifc{ifcClass=Just "IF"} -> IF
              Ifc{ifcClass=Just "UF"} -> UF
              Ifc{ifcClass=Just "OF"} -> OF
              
              -- code for interfaces without CLASS comes from old FPA.hs
              _ | (not.null.ifcParams)  ifc -> IF  -- In case there are editable relations, this must be an import function.
                | (isUni.objctx.ifcObj) ifc -> OF  -- If there is max one atom, this is a simple function.
                | otherwise                 -> UF  -- Otherwise, it is a UF
    in FP tp nm cmplxty
  where depth2Cmplxty :: Int -> Complexity
        depth2Cmplxty d | d <= 1    = Eenvoudig
                        | d == 2    = Gemiddeld
                        | otherwise = Moeilijk 

        getDepth Obj{objmsub=Nothing}             = 0
        getDepth Obj{objmsub=Just InterfaceRef{}} = 1 -- TODO: shouldn't we follow the ref? (this def. is from old FPA.hs)
        getDepth Obj{objmsub=Just (Box _ _ objs)}   = 1 + maximum (map getDepth objs)

class ShowLang a where
  showLang :: Lang -> a -> String

instance ShowLang FP where
  showLang lang fp = showLang lang (fpType fp) ++ " " ++ showLang lang (fpComplexity fp)


instance ShowLang FPType where
  showLang Dutch   ILGV = "ILGV" 
  showLang Dutch   KGV  = "KGV" 
  showLang Dutch   IF   = "IF"
  showLang Dutch   UF   = "UF"
  showLang Dutch   OF   = "OF"
  showLang English ILGV = "ILF" -- Internal Logical File 
  showLang English KGV  = "ELF" -- External Logical File 
  showLang English IF   = "EI"  -- External Input
  showLang English UF   = "EO"  -- External Output
  showLang English OF   = "EQ"  -- External inQuiry

instance ShowLang Complexity where
  showLang Dutch   Eenvoudig = "Eenvoudig" 
  showLang Dutch   Gemiddeld = "Gemiddeld"
  showLang Dutch   Moeilijk  = "Moeilijk"
  showLang English Eenvoudig = "Low" 
  showLang English Gemiddeld = "Average"
  showLang English Moeilijk  = "High"
