y :: Lang           -- ^ The currently used 'Lang'uage.
          -> PageConf       -- ^ More specific information of the current web page being created.
          -> Table          -- ^ The source 'Table' to use when creating the web page.
          -> Config         -- ^ 'Config'uration containing specific information about the source 'Table' and translation data.
          -> Html           -- ^ The generated HTML code.
tableBody lang page (Table time _ tableContent) cnf@Config{..} =
    let
        -- | Helper function to get the 'I18N' translation of the input.
        i18nTranslationOf :: Text   -- ^ The 'Text' to translate.
                          -> Text   -- ^ Translation of the input.
        i18nTranslationOf            = toLang i18n lang

        {- | Overriding the translations for some 'String's, for usage with the
            /periodi/ column of the 'Table'.
        -}
        i18nTranslationOfPeriod :: Text     -- ^ The cell content to be translated.
                                -> Text     -- ^ The translation.
        i18nTranslationOfPeriod cell
                                | "?"      <- cell = cell
                                | "I"      <- cell = cell
                                | "I-II"   <- cell = cell
                                | "I-III"  <- cell = cell
                                | "I-IV"   <- cell = cell
                                | "I, III" <- cell = cell
                                | "II"     <- cell = cell
                                | "II-III" <- cell = cell
                                | "II-IV"  <- cell = cell
                                | "II, IV" <- cell = cell
                                | "III"    <- cell = cell
                                | "III-IV" <- cell = cell
                                | "IV"     <- cell = cell
                                | ""       <- cell = cell
                                -- If non of the above \"exceptions\" is found in the listing,
                                -- the cell content will be translated.
                                | otherwise        = i18nTranslationOf cell

        -- | Helper method to fetch a translation of the 'Course' name
        translateCourseName :: Text         -- ^ WebOodi course code.
                            -> Maybe Text   -- ^ Translation of the 'Course' name.
        translateCourseName     code = unsafePerformIO $
            runReaderT (i18nCourseNameFromOodi lang code) cnf


        -- Helper functions to access the different column header properties
        colTitle          col = columnTitle columnHeaders col
        colCodeTitle          = colTitle "colCode"
        colCourseNameTitle    = colTitle "colCourseName"
        colPeriodTitle        = colTitle "colPeriod"
        colRepeatsTitle       = colTitle "colRepeats"
        colLangTitle          = colTitle "colLang"
        colWebsiteTitle       = colTitle "colWebsite"

        colWidth          col = columnWidth columnHeaders col
        colCodeWidth          = colWidth "colCode"
        colCourseNameWidth    = colWidth "colCourseName"
        colPeriodWidth        = colWidth "colPeriod"
        colRepeatsWidth       = colWidth "colRepeats"
        colLangWidth          = colWidth "colLang"
        colWebsiteWidth       = colWidth "colWebsite"


        ------------------------------------------------------------------------
        -- course table
        ------------------------------------------------------------------------

        {- | Function for generating the nested @\<div.courses\>@ HTML-tags.

            If the maximum number of @categories@ that can be nested not is
            reached it'll continue trying to add 'Category' titles until the
            maximum is reached. After that the 'Table' row is created.
        -}
        courseTable n rows
            | n < length categories = withCat n rows $ courseTable (n + 1)
            | otherwise             =
                [shamlet|
                    <table>
                        $forall c <- rows
                            <tr data-taso="#{fromMaybe "" $ catAt cnf categoryLevel c}"
                                data-kieli="#{getCellContent colLangTitle c}"
                                data-lukukausi="#{getCellContent "lukukausi" c}"
                                data-pidetaan="#{getCellContent "pidetään" c}">

                                <td style="width:#{colCodeWidth}">
                                    <a href="#{weboodiLink weboodiUrl lang $ getCellContent colCodeTitle c}">
                                        <b>#{getCellContent colCodeTitle c}

                                <td style="width:#{colCourseNameWidth}">

                                    $maybe name <- translateCourseName (getCellContent colCodeTitle c)
                                        #{name}
                                    $nothing
                                        #{getCellContent colCourseNameTitle c}

                                    $with op <- getCellContent "op" c
                                        $if not (T.null op)
                                            \ (#{op} #{i18nTranslationOf "op"})

                                <td.compact style="width:#{colPeriodWidth}"  title="#{getCellContent colPeriodTitle c}">#{i18nTranslationOfPeriod $ getCellContent colPeriodTitle c}
                                <td.compact style="width:#{colRepeatsWidth}" title="#{getCellContent colRepeatsTitle c}">#{getCellContent colRepeatsTitle c}
                                <td.compact style="width:#{colLangWidth};font-family:monospace" title="#{getCellContent colLangTitle c}">
                                    $case T.words (getCellContent colLangTitle c)
                                        $of []
                                        $of rows
                                            <b>#{head rows}
                                            $if null $ tail rows
                                            $else
                                                (#{T.intercalate ", " $ tail rows})

                                <td.compact style="width:#{colWebsiteWidth}" title="#{colWebsiteTitle}">
                                    $maybe p <- getCellContentMaybe colWebsiteTitle c
                                        $if not (T.null p)
                                            \ #
                                            <a href="#{p}">#{i18nTranslationOf colWebsiteTitle}
                |]


        ------------------------------------------------------------------------
        -- if it begins with a number, apply appropriate header
        ------------------------------------------------------------------------

        {- | Helper function to select the correct HTML-header tag for the
            current 'Category'.
        -}
        catHeader n category =
            [shamlet|
                $case n
                    $of 0
                        <h1>
                            #{translation}
                    $of 1
                        <h2>
                            #{translation}
                    $of 2
                        <h3>
                            <i>#{translation}
                    $of 3
                        <h4>
                            #{translation}
                    $of 4
                        <h5>
                            #{translation}
                    $of 5
                        <h6>
                            #{translation}
                    $of _
                        <b>
                            #{translation}
            |]
          where
            translation = i18nTranslationOf category

        ------------------------------------------------------------------------
        -- course category div
        ------------------------------------------------------------------------

        {- | Helper function to select if the HTML-header should be added or if
            the content @\<div.courses\>@ tags should nested deeper.
            
            Group the 'Table' rows into lists of 'Table' rows based on the
            'Category' they belong to. Then loop over all 'Category' based
            lists to generate the different listings.
        -}
        withCat n rows f =
            [shamlet|
                $forall groupedRows <- L.groupBy (catGroup cnf n) rows
                    <div.courses>
                        $maybe category <- catAt cnf n (head groupedRows)
                            #{catHeader n category}
                        #{f groupedRows}
            |]


        ------------------------------------------------------------------------
        -- put everything together
        ------------------------------------------------------------------------

    in [shamlet|
        \<!-- title: #{lookupLang lang $ pageTitle page} -->
        \<!-- fi (Suomenkielinen versio): #{toUrlPath $ lookupLang "fi" $ pageUrl page} -->
        \<!-- se (Svensk version): #{toUrlPath $ lookupLang "se" $ pageUrl page} -->
        \<!-- en (English version): #{toUrlPath $ lookupLang "en" $ pageUrl page} -->
        \

        \<!-- !!! IMPORTANT !!! -->
        \<!-- THIS PAGE IS GENERATED AUTOMATICALLY - DO NOT EDIT DIRECTLY! -->
        \<!-- See https://github.com/SimSaladin/opetussivut for information on how to edit instead -->
        \

        <p>
            $with pg <- head pages
                <a href="#{toUrlPath $ lookupLang lang $ pageUrl pg}">#{lookupLang lang $ pageTitle pg}
            $forall pg <- tail pages
                |
                <a href="#{toUrlPath $ lookupLang lang $ pageUrl pg}">#{lookupLang lang $ pageTitle pg}


        #{markdown def $ LT.fromStrict $ i18nTranslationOf "aputeksti"}


        <div>
            <div.buttons>
                #{i18nTranslationOf "Kieli"}:&nbsp;
                <select id="select-kieli" name="kieli" onchange="updateList(this)">
                    <option value="any">#{i18nTranslationOf "Kaikki"}
                    $forall l <- languages
                        <option value="#{l}">#{i18nTranslationOf l}

                #{i18nTranslationOf "Taso"}:&nbsp;
                <select id="select-taso" name="taso" onchange="updateList(this)">
                    <option value="any">#{i18nTranslationOf "Kaikki"}
                    $forall cat <- (categories !! categoryLevel)
                        <option value="#{cat}">#{i18nTranslationOf cat}

                #{i18nTranslationOf "Lukukausi"}:&nbsp;
                <select id="select-lukukausi" name="lukukausi" onchange="updateList(this)">
                    <option value="any"   >#{i18nTranslationOf "Kaikki"}
                    <option value="syksy" >#{i18nTranslationOf "Syksy"}
                    <option value="kevät" >#{i18nTranslationOf "Kevät"}
                    <option value="kesä"  >#{i18nTranslationOf "Kesä"}

            <div.headers>
                <table style="width:100%">
                    <tr>
                        <td style="width:#{colCodeWidth}">#{i18nTranslationOf colCodeTitle}
                        <td style="width:#{colCourseNameWidth}">#{i18nTranslationOf colCourseNameTitle}
                        <td style="width:#{colPeriodWidth}" >#{i18nTranslationOf colPeriodTitle}
                        <td style="width:#{colRepeatsWidth}" >#{i18nTranslationOf colRepeatsTitle}
                        <td style="width:#{colLangWidth}" >#{i18nTranslationOf colLangTitle}
                        <td style="width:#{colWebsiteWidth}">#{i18nTranslationOf colWebsiteTitle}

            #{courseTable 0 $ tableContent}

        <p>
            #{i18nTranslationOf "Päivitetty"} #{show time}

        <style>
            .buttons {
                padding:1em;
            }

            .headers table {
                width:100%;
                table-layout:fixed;
            }

            .courses table {
                width:100%;
                table-layout:fixed;
            }

            .courses td.compact {
                overflow:hidden;
                text-overflow:ellipsis;
                white-space:nowrap;
            }

            tr[data-pidetaan="next-year"] {
                color:gray;
            }


        <script type="text/javascript">
            #{preEscapedToHtml $ renderJavascript $ jsLogic undefined}
    |]


{- | Creatin
tableBody :: Lang           -- ^ The currently used 'Lang'uage.
          -> PageConf       -- ^ More specific information of the current web page being created.
          -> Table          -- ^ The source 'Table' to use when creating the web page.
          -> Config         -- ^ 'Config'uration containing specific information about the source 'Table' and translation data.
          -> Html
tableBody lang page (Table time _ tableContent) cnf@Config{..} =
    let
        -- | Helper function to get the 'I18N' translation of the input.
        i18nTranslationOf :: Text   -- ^ The 'Text' to translate.
                          -> Text   -- ^ Translation of the input.
        i18nTranslationOf            = toLang i18n lang

        {- | Overriding the translations for some 'String's, for usage with the
            /periodi/ column of the 'Table'.
        -}
        i18nTranslationOfPeriod :: Text     -- ^ The cell content to be translated.
                                -> Text     -- ^ The translation.
        i18nTranslationOfPeriod cell
                                | "?"      <- cell = cell
                                | "I"      <- cell = cell
                                | "I-II"   <- cell = cell
                                | "I-III"  <- cell = cell
                                | "I-IV"   <- cell = cell
                                | "I, III" <- cell = cell
                                | "II"     <- cell = cell
                                | "II-III" <- cell = cell
                                | "II-IV"  <- cell = cell
                                | "II, IV" <- cell = cell
                                | "III"    <- cell = cell
                                | "III-IV" <- cell = cell
                                | "IV"     <- cell = cell
                                | ""       <- cell = cell
                                -- If non of the above \"exceptions\" is found in the listing,
                                -- the cell content will be translated.
                                | otherwise        = i18nTranslationOf cell

        -- | Helper method to fetch a translation of the 'Course' name
        translateCourseName :: Text         -- ^ WebOodi course code.
                            -> Maybe Text   -- ^ Translation of the 'Course' name.
        translateCourseName     code = unsafePerformIO $
            runReaderT (i18nCourseNameFromOodi lang code) cnf


        -- Helper functions to access the different column header properties
        colTitle          col = columnTitle columnHeaders col
        colCodeTitle          = colTitle "colCode"
        colCourseNameTitle    = colTitle "colCourseName"
        colPeriodTitle        = colTitle "colPeriod"
        colRepeatsTitle       = colTitle "colRepeats"
        colLangTitle          = colTitle "colLang"
        colWebsiteTitle       = colTitle "colWebsite"

        colWidth          col = columnWidth columnHeaders col
        colCodeWidth          = colWidth "colCode"
        colCourseNameWidth    = colWidth "colCourseName"
        colPeriodWidth        = colWidth "colPeriod"
        colRepeatsWidth       = colWidth "colRepeats"
        colLangWidth          = colWidth "colLang"
        colWebsiteWidth       = colWidth "colWebsite"


        ------------------------------------------------------------------------
        -- course table
        ------------------------------------------------------------------------

        {- | Function for generating the nested @\<div.courses\>@ HTML-tags.

            If the maximum number of @categories@ that can be nested not is
            reached it'll continue trying to add 'Category' titles until the
            maximum is reached. After that the 'Table' row is created.
        -}
        courseTable n rows
            | n < length categories = withCat n rows $ courseTable (n + 1)
            | otherwise             =
                [shamlet|
                    <table>
                        $forall c <- rows
                            <tr data-taso="#{fromMaybe "" $ catAt cnf categoryLevel c}"
                                data-kieli="#{getCellContent colLangTitle c}"
                                data-lukukausi="#{getCellContent "lukukausi" c}"
                                data-pidetaan="#{getCellContent "pidetään" c}">

                                <td style="width:#{colCodeWidth}">
                                    <a href="#{weboodiLink weboodiUrl lang $ getCellContent colCodeTitle c}">
                                        <b>#{getCellContent colCodeTitle c}

                                <td style="width:#{colCourseNameWidth}">

                                    $maybe name <- translateCourseName (getCellContent colCodeTitle c)
                                        #{name}
                                    $nothing
                                        #{getCellContent colCourseNameTitle c}

                                    $with op <- getCellContent "op" c
                                        $if not (T.null op)
                                            \ (#{op} #{i18nTranslationOf "op"})

                                <td.compact style="width:#{colPeriodWidth}"  title="#{getCellContent colPeriodTitle c}">#{i18nTranslationOfPeriod $ getCellContent colPeriodTitle c}
                                <td.compact style="width:#{colRepeatsWidth}" title="#{getCellContent colRepeatsTitle c}">#{getCellContent colRepeatsTitle c}
                                <td.compact style="width:#{colLangWidth};font-family:monospace" title="#{getCellContent colLangTitle c}">
                                    $case T.words (getCellContent colLangTitle c)
                                        $of []
                                        $of rows
                                            <b>#{head rows}
                                            $if null $ tail rows
                                            $else
                                                (#{T.intercalate ", " $ tail rows})

                                <td.compact style="width:#{colWebsiteWidth}" title="#{colWebsiteTitle}">
                                    $maybe p <- getCellContentMaybe colWebsiteTitle c
                                        $if not (T.null p)
                                            \ #
                                            <a href="#{p}">#{i18nTranslationOf colWebsiteTitle}
                |]


        ------------------------------------------------------------------------
        -- if it begins with a number, apply appropriate header
        ------------------------------------------------------------------------

        {- | Helper function to select the correct HTML-header tag for the
            current 'Category'.
        -}
        catHeader n category =
            [shamlet|
                $case n
                    $of 0
                        <h1>
                            #{translation}
                    $of 1
                        <h2>
                            #{translation}
                    $of 2
                        <h3>
                            <i>#{translation}
                    $of 3
                        <h4>
                            #{translation}
                    $of 4
                        <h5>
                            #{translation}
                    $of 5
                        <h6>
                            #{translation}
                    $of _
                        <b>
                            #{translation}
            |]
          where
            translation = i18nTranslationOf category

        ------------------------------------------------------------------------
        -- course category div
        ------------------------------------------------------------------------

        {- | Helper function to select if the HTML-header should be added or if
            the content @\<div.courses\>@ tags should nested deeper.
            
            Group the 'Table' rows into lists of 'Table' rows based on the
            'Category' they belong to. Then loop over all 'Category' based
            lists to generate the different listings.
        -}
        withCat n rows f =
            [shamlet|
                $forall groupedRows <- L.groupBy (catGroup cnf n) rows
                    <div.courses>
                        $maybe category <- catAt cnf n (head groupedRows)
                            #{catHeader n category}
                        #{f groupedRows}
            |]


        ------------------------------------------------------------------------
        -- put everything together
        ------------------------------------------------------------------------

    in [shamlet|
        \<!-- title: #{lookupLang lang $ pageTitle page} -->
        \<!-- fi (Suomenkielinen versio): #{toUrlPath $ lookupLang "fi" $ pageUrl page} -->
        \<!-- se (Svensk version): #{toUrlPath $ lookupLang "se" $ pageUrl page} -->
        \<!-- en (English version): #{toUrlPath $ lookupLang "en" $ pageUrl page} -->
        \

        \<!-- !!! IMPORTANT !!! -->
        \<!-- THIS PAGE IS GENERATED AUTOMATICALLY - DO NOT EDIT DIRECTLY! -->
        \<!-- See https://github.com/SimSaladin/opetussivut for information on how to edit instead -->
        \

        <p>
            $with pg <- head pages
                <a href="#{toUrlPath $ lookupLang lang $ pageUrl pg}">#{lookupLang lang $ pageTitle pg}
            $forall pg <- tail pages
                |
                <a href="#{toUrlPath $ lookupLang lang $ pageUrl pg}">#{lookupLang lang $ pageTitle pg}


        #{markdown def $ LT.fromStrict $ i18nTranslationOf "aputeksti"}


        <div>
            <div.buttons>
                #{i18nTranslationOf "Kieli"}:&nbsp;
                <select id="select-kieli" name="kieli" onchange="updateList(this)">
                    <option value="any">#{i18nTranslationOf "Kaikki"}
                    $forall l <- languages
                        <option value="#{l}">#{i18nTranslationOf l}

                #{i18nTranslationOf "Taso"}:&nbsp;
                <select id="select-taso" name="taso" onchange="updateList(this)">
                    <option value="any">#{i18nTranslationOf "Kaikki"}
                    $forall cat <- (categories !! categoryLevel)
                        <option value="#{cat}">#{i18nTranslationOf cat}

                #{i18nTranslationOf "Lukukausi"}:&nbsp;
                <select id="select-lukukausi" name="lukukausi" onchange="updateList(this)">
                    <option value="any"   >#{i18nTranslationOf "Kaikki"}
                    <option value="syksy" >#{i18nTranslationOf "Syksy"}
                    <option value="kevät" >#{i18nTranslationOf "Kevät"}
                    <option value="kesä"  >#{i18nTranslationOf "Kesä"}

            <div.headers>
                <table style="width:100%">
                    <tr>
                        <td style="width:#{colCodeWidth}">#{i18nTranslationOf colCodeTitle}
                        <td style="width:#{colCourseNameWidth}">#{i18nTranslationOf colCourseNameTitle}
                        <td style="width:#{colPeriodWidth}" >#{i18nTranslationOf colPeriodTitle}
                        <td style="width:#{colRepeatsWidth}" >#{i18nTranslationOf colRepeatsTitle}
                        <td style="width:#{colLangWidth}" >#{i18nTranslationOf colLangTitle}
                        <td style="width:#{colWebsiteWidth}">#{i18nTranslationOf colWebsiteTitle}

            #{courseTable 0 $ tableContent}

        <p>
            #{i18nTranslationOf "Päivitetty"} #{show time}

        <style>
            .buttons {
                padding:1em;
            }

            .headers table {
                width:100%;
                table-layout:fixed;
            }

            .courses table {
                width:100%;
                table-layout:fixed;
            }

            .courses td.compact {
                overflow:hidden;
                text-overflow:ellipsis;
                white-space:nowrap;
            }

            tr[data-pidetaan="next-year"] {
                color:gray;
            }


        <script type="text/javascript">
            #{preEscapedToHtml $ renderJavascript $ jsLogic undefined}
    |]
tableBody lang page (Table time _ tableContent) cnf@Config{..} =
    let
        -- | Helper function to get the 'I18N' translation of the input.
        i18nTranslationOf :: Text   -- ^ The 'Text' to translate.
                          -> Text   -- ^ Translation of the input.
        i18nTranslationOf            = toLang i18n lang

        {- | Overriding the translations for some 'String's, for usage with the
            /periodi/ column of the 'Table'.
        -}
        i18nTranslationOfPeriod :: Text     -- ^ The cell content to be translated.
                                -> Text     -- ^ The translation.
        i18nTranslationOfPeriod cell
                                | "?"      <- cell = cell
                                | "I"      <- cell = cell
                                | "I-II"   <- cell = cell
                                | "I-III"  <- cell = cell
                                | "I-IV"   <- cell = cell
                                | "I, III" <- cell = cell
                                | "II"     <- cell = cell
                                | "II-III" <- cell = cell
                                | "II-IV"  <- cell = cell
                                | "II, IV" <- cell = cell
                                | "III"    <- cell = cell
                                | "III-IV" <- cell = cell
                                | "IV"     <- cell = cell
                                | ""       <- cell = cell
                                -- If non of the above \"exceptions\" is found in the listing,
                                -- the cell content will be translated.
                                | otherwise        = i18nTranslationOf cell

        -- | Helper method to fetch a translation of the 'Course' name
        translateCourseName :: Text         -- ^ WebOodi course code.
                            -> Maybe Text   -- ^ Translation of the 'Course' name.
        translateCourseName     code = unsafePerformIO $
            runReaderT (i18nCourseNameFromOodi lang code) cnf


        -- Helper functions to access the different column header properties
        colTitle          col = columnTitle columnHeaders col
        colCodeTitle          = colTitle "colCode"
        colCourseNameTitle    = colTitle "colCourseName"
        colPeriodTitle        = colTitle "colPeriod"
        colRepeatsTitle       = colTitle "colRepeats"
        colLangTitle          = colTitle "colLang"
        colWebsiteTitle       = colTitle "colWebsite"

        colWidth          col = columnWidth columnHeaders col
        colCodeWidth          = colWidth "colCode"
        colCourseNameWidth    = colWidth "colCourseName"
        colPeriodWidth        = colWidth "colPeriod"
        colRepeatsWidth       = colWidth "colRepeats"
        colLangWidth          = colWidth "colLang"
        colWebsiteWidth       = colWidth "colWebsite"


        ------------------------------------------------------------------------
        -- course table
        ------------------------------------------------------------------------

        {- | Function for generating the nested @\<div.courses\>@ HTML-tags.

            If the maximum number of @categories@ that can be nested not is
            reached it'll continue trying to add 'Category' titles until the
            maximum is reached. After that the 'Table' row is created.
        -}
        courseTable n rows
            | n < length categories = withCat n rows $ courseTable (n + 1)
            | otherwise             =
                [shamlet|
                    <table>
                        $forall c <- rows
                            <tr data-taso="#{fromMaybe "" $ catAt cnf categoryLevel c}"
                                data-kieli="#{getCellContent colLangTitle c}"
                                data-lukukausi="#{getCellContent "lukukausi" c}"
                                data-pidetaan="#{getCellContent "pidetään" c}">

                                <td style="width:#{colCodeWidth}">
                                    <a href="#{weboodiLink weboodiUrl lang $ getCellContent colCodeTitle c}">
                                        <b>#{getCellContent colCodeTitle c}

                                <td style="width:#{colCourseNameWidth}">

                                    $maybe name <- translateCourseName (getCellContent colCodeTitle c)
                                        #{name}
                                    $nothing
                                        #{getCellContent colCourseNameTitle c}

                                    $with op <- getCellContent "op" c
                                        $if not (T.null op)
                                            \ (#{op} #{i18nTranslationOf "op"})

                                <td.compact style="width:#{colPeriodWidth}"  title="#{getCellContent colPeriodTitle c}">#{i18nTranslationOfPeriod $ getCellContent colPeriodTitle c}
                                <td.compact style="width:#{colRepeatsWidth}" title="#{getCellContent colRepeatsTitle c}">#{getCellContent colRepeatsTitle c}
                                <td.compact style="width:#{colLangWidth};font-family:monospace" title="#{getCellContent colLangTitle c}">
                                    $case T.words (getCellContent colLangTitle c)
                                        $of []
                                        $of rows
                                            <b>#{head rows}
                                            $if null $ tail rows
                                            $else
                                                (#{T.intercalate ", " $ tail rows})

                                <td.compact style="width:#{colWebsiteWidth}" title="#{colWebsiteTitle}">
                                    $maybe p <- getCellContentMaybe colWebsiteTitle c
                                        $if not (T.null p)
                                            \ #
                                            <a href="#{p}">#{i18nTranslationOf colWebsiteTitle}
                |]


        ------------------------------------------------------------------------
        -- if it begins with a number, apply appropriate header
        ------------------------------------------------------------------------

        {- | Helper function to select the correct HTML-header tag for the
            current 'Category'.
        -}
        catHeader n category =
            [shamlet|
                $case n
                    $of 0
                        <h1>
                            #{translation}
                    $of 1
                        <h2>
                            #{translation}
                    $of 2
                        <h3>
                            <i>#{translation}
                    $of 3
                        <h4>
                            #{translation}
                    $of 4
                        <h5>
                            #{translation}
                    $of 5
                        <h6>
                            #{translation}
                    $of _
                        <b>
                            #{translation}
            |]
          where
            translation = i18nTranslationOf category

        ------------------------------------------------------------------------
        -- course category div
        ------------------------------------------------------------------------

        {- | Helper function to select if the HTML-header should be added or if
            the content @\<div.courses\>@ tags should nested deeper.
            
            Group the 'Table' rows into lists of 'Table' rows based on the
            'Category' they belong to. Then loop over all 'Category' based
            lists to generate the different listings.
        -}
        withCat n rows f =
            [shamlet|
                $forall groupedRows <- L.groupBy (catGroup cnf n) rows
                    <div.courses>
                        $maybe category <- catAt cnf n (head groupedRows)
                            #{catHeader n category}
                        #{f groupedRows}
            |]


        ------------------------------------------------------------------------
        -- put everything together
        ------------------------------------------------------------------------

    in [shamlet|
        \<!-- title: #{lookupLang lang $ pageTitle page} -->
        \<!-- fi (Suomenkielinen versio): #{toUrlPath $ lookupLang "fi" $ pageUrl page} -->
        \<!-- se (Svensk version): #{toUrlPath $ lookupLang "se" $ pageUrl page} -->
        \<!-- en (English version): #{toUrlPath $ lookupLang "en" $ pageUrl page} -->
        \

        \<!-- !!! IMPORTANT !!! -->
        \<!-- THIS PAGE IS GENERATED AUTOMATICALLY - DO NOT EDIT DIRECTLY! -->
        \<!-- See https://github.com/SimSaladin/opetussivut for information on how to edit instead -->
        \

        <p>
            $with pg <- head pages
                <a href="#{toUrlPath $ lookupLang lang $ pageUrl pg}">#{lookupLang lang $ pageTitle pg}
            $forall pg <- tail pages
                |
                <a href="#{toUrlPath $ lookupLang lang $ pageUrl pg}">#{lookupLang lang $ pageTitle pg}


        #{markdown def $ LT.fromStrict $ i18nTranslationOf "aputeksti"}


        <div>
            <div.buttons>
                #{i18nTranslationOf "Kieli"}:&nbsp;
                <select id="select-kieli" name="kieli" onchange="updateList(this)">
                    <option value="any">#{i18nTranslationOf "Kaikki"}
                    $forall l <- languages
                        <option value="#{l}">#{i18nTranslationOf l}

                #{i18nTranslationOf "Taso"}:&nbsp;
                <select id="select-taso" name="taso" onchange="updateList(this)">
                    <option value="any">#{i18nTranslationOf "Kaikki"}
                    $forall cat <- (categories !! categoryLevel)
                        <option value="#{cat}">#{i18nTranslationOf cat}

                #{i18nTranslationOf "Lukukausi"}:&nbsp;
                <select id="select-lukukausi" name="lukukausi" onchange="updateList(this)">
                    <option value="any"   >#{i18nTranslationOf "Kaikki"}
                    <option value="syksy" >#{i18nTranslationOf "Syksy"}
                    <option value="kevät" >#{i18nTranslationOf "Kevät"}
                    <option value="kesä"  >#{i18nTranslationOf "Kesä"}

            <div.headers>
                <table style="width:100%">
                    <tr>
                        <td style="width:#{colCodeWidth}">#{i18nTranslationOf colCodeTitle}
                        <td style="width:#{colCourseNameWidth}">#{i18nTranslationOf colCourseNameTitle}
                        <td style="width:#{colPeriodWidth}" >#{i18nTranslationOf colPeriodTitle}
                        <td style="width:#{colRepeatsWidth}" >#{i18nTranslationOf colRepeatsTitle}
                        <td style="width:#{colLangWidth}" >#{i18nTranslationOf colLangTitle}
                        <td style="width:#{colWebsiteWidth}">#{i18nTranslationOf colWebsiteTitle}

            #{courseTable 0 $ tableContent}

        <p>
            #{i18nTranslationOf "Päivitetty"} #{show time}

        <style>
            .buttons {
                padding:1em;
            }

            .headers table {
                width:100%;
                table-layout:fixed;
            }

            .courses table {
                width:100%;
                table-layout:fixed;
            }

            .courses td.compact {
                overflow:hidden;
                text-overflow:ellipsis;
                white-space:nowrap;
            }

            tr[data-pidetaan="next-year"] {
                color:gray;
            }


        <script type="text/javascript">
            #{preEscapedToHtml $ renderJavascript $ jsLogic undefined}
    |]