-- Copyright (c) David Amos, 2008-2011. All rights reserved.

-- |Constructions of the finite geometries AG(n,Fq) and PG(n,Fq), their points, lines and flats,
-- together with the incidence graphs between points and lines.
module Math.Combinatorics.FiniteGeometry where

import Data.List as L
import qualified Data.Set as S

import Math.Common.ListSet (toListSet)
import Math.Core.Utils

import Math.Core.Field
import Math.Algebra.LinearAlgebra -- hiding ( det )

import Math.Combinatorics.Graph
import Math.Combinatorics.GraphAuts -- for use in GHCi
import Math.Algebra.Group.PermutationGroup hiding (elts) -- for use in GHCi
import Math.Algebra.Group.SchreierSims as SS hiding (elts) -- for use in GHCi

-- !! The following two functions previously required (FiniteField a) as context
-- but this has been temporarily removed to enable them to work with Math.Core.Field

-- |ptsAG n fq returns the points of the affine geometry AG(n,Fq), where fq are the elements of Fq
ptsAG :: Int -> [a] -> [[a]]
ptsAG 0 fq = [[]]
ptsAG n fq = [x:xs | x <- fq, xs <- ptsAG (n-1) fq]

-- |ptsPG n fq returns the points of the projective geometry PG(n,Fq), where fq are the elements of Fq
ptsPG :: Num a => Int -> [a] -> [[a]]
ptsPG 0 _ = [[1]]
ptsPG n fq = map (0:) (ptsPG (n-1) fq) ++ map (1:) (ptsAG n fq)


-- "projective normal form"
pnf (0:xs) = 0 : pnf xs
pnf (1:xs) = 1 : xs
pnf (x:xs) = 1 : map (* x') xs where x' = recip x

ispnf (0:xs) = ispnf xs
ispnf (1:xs) = True
ispnf _ = False

-- closure of points in AG(n,Fq)
-- given p1, .., pk, we're looking for all a1 p1 + ... + ak pk, s.t. a1 + ... + ak = 1
-- if m is the matrix with p1, .., pk as rows, and vs are the vectors [a1, .., ak]
-- then this is the same as [v <*>> m | v <- vs] == [m' <<*> v | v <- vs]

-- |Given a list of points in AG(n,Fq), return their closure, the smallest flat containing them
closureAG :: (Num a, Ord a, FinSet a) => [[a]] -> [[a]]
closureAG ps =
    let vs = [ (1 - sum xs) : xs | xs <- ptsAG (k-1) fq ] -- k-vectors over fq whose sum is 1
    in toListSet [m' <<*> v | v <- vs]
    where k = length ps -- the dimension of the flat (assuming ps are independent)
          m' = L.transpose ps
          fq = elts
-- toListSet call sorts the result, and also removes duplicates in case the points weren't independent

{-
closureAG ps =
    let vs = [ (1 - sum xs) : xs | xs <- ptsAG (k-1) fq ] -- k-vectors over fq whose sum is 1
    in toListSet [foldl1 (<+>) $ zipWith (*>) v ps | v <- vs]
    where k = length ps        -- the dimension of the flat
          fq = eltsFq undefined
-}

lineAG [p1,p2] = L.sort [ p1 <+> (c *> dp) | c <- fq ] where
    dp = p2 <-> p1
    fq = elts

-- closure of points in PG(n,Fq)
-- take all linear combinations of the points (ie the subspace generated by the points, considered as points in Fq ^(n+1) )
-- then discard all which aren't in PNF (thus dropping back into PG(n,Fq))

-- |Given a set of points in PG(n,Fq), return their closure, the smallest flat containing them
closurePG :: (Num a, Ord a, FinSet a) => [[a]] -> [[a]]
closurePG ps = toListSet $ filter ispnf $ map (<*>> ps) $ ptsAG k fq where
    k = length ps
    fq = elts
-- toListSet call sorts the result, and also removes duplicates in case the points weren't independent

linePG [p1,p2] = toListSet $ filter ispnf [(a *> p1) <+> (b *> p2) | a <- fq, b <- fq]
    where fq = elts

-- van Lint & Wilson, p325, 332
qtorial n q | n >= 0 = product [(q^i - 1) `div` (q-1) | i <- [1..n]]

-- van Lint & Wilson, p326
qnomial n k q = (n `qtorial` q) `div` ( (k `qtorial` q) * ((n-k) `qtorial` q) )

-- Cameron, p129
numFlatsPG n q k = qnomial (n+1) (k+1) q -- because it's the number of subspaces in AG n+1 

-- Cameron, p137
numFlatsAG n q k = q^(n-k) * qnomial n k q


qtorials q = scanl (*) 1 [(q^i - 1) `div` (q-1) | i <- [1..]]

qnomials q = iterate succ [1] where
    succ xs = L.zipWith3 (\l r c -> l+c*r) (0:xs) (xs++[0]) (iterate (*q) 1)
    -- succ xs = zipWith (+) (0:xs) $ zipWith (*) (xs++[0]) $ iterate (*q) 1 
-- This amounts to saying
-- [n+1,k]_q = [n,k-1]_q + q^k [n,k]_q
-- Cameron, Combinatorics, p126


-- FLATS VIA REDUCED ROW ECHELON FORMS
-- Suggested by Cameron p125

data ZeroOneStar = Zero | One | Star deriving (Eq)

instance Show ZeroOneStar where
    show Zero = "0"
    show One  = "1"
    show Star = "*"

-- reduced row echelon forms
rrefs n k = map (rref 1 1) (combinationsOf k [1..n]) where
    rref r c (x:xs) =
        if c == x
        then zipWith (:) (oneColumn r) (rref (r+1) (c+1) xs)
        else zipWith (:) (starColumn r) (rref r (c+1) (x:xs))
    rref _ c [] = replicate k (replicate (n+1-c) Star)
    oneColumn r = replicate (r-1) Zero ++ One : replicate (k-r) Zero
    starColumn r = replicate (r-1) Star ++ replicate (k+1-r) Zero


-- flatsPG :: (FiniteField a) => Int -> [a] -> Int -> [[[a]]]

-- |@flatsPG n fq k@ returns the k-flats in PG(n,Fq), where fq are the elements of Fq.
-- The returned flats are represented as matrices in reduced row echelon form,
-- the rows of which are the points that generate the flat.
-- The full set of points in the flat can be recovered by calling 'closurePG'
flatsPG :: (Eq a, Num a) => Int -> [a] -> Int -> [[[a]]]
flatsPG n fq k = concatMap substStars $ rrefs (n+1) (k+1) where
    substStars (r:rs) = [r':rs' | r' <- substStars' r, rs' <- substStars rs]
    substStars [] = [[]]
    substStars' (Star:xs) = [x':xs' | x' <- fq, xs' <- substStars' xs]
    substStars' (Zero:xs) = map (0:) $ substStars' xs
    substStars' (One:xs) = map (1:) $ substStars' xs
    substStars' [] = [[]]


-- Flats in AG(n,Fq) are just the flats in PG(n,Fq) which are not "at infinity"
-- flatsAG :: (FiniteField a) => Int -> [a] -> Int -> [[[a]]]

-- |flatsAG n fq k returns the k-flats in AG(n,Fq), where fq are the elements of Fq.
flatsAG :: (Eq a, Num a) => Int -> [a] -> Int -> [[[a]]]
flatsAG n fq k = [map tail (r : map (r <+>) rs) | r:rs <- flatsPG n fq k, head r == 1]
-- The head r == 1 condition is saying that we want points which are in the "finite" part of PG(n,Fq), not points at infinity
-- The reason we add r to each of the rs is to bring them into the "finite" part
-- (If you don't do this, it can lead to incorrect results, for example some of the flats having the same closure)


-- linesPG :: (FiniteField a) => Int -> [a] -> [[[a]]]

-- |The lines (1-flats) in PG(n,fq)
linesPG :: (Eq a, Num a) => Int -> [a] -> [[[a]]]
linesPG n fq = flatsPG n fq 1

-- linesAG :: (FiniteField a) => Int -> [a] -> [[[a]]]

-- |The lines (1-flats) in AG(n,fq)
linesAG :: (Eq a, Num a) => Int -> [a] -> [[[a]]]
linesAG n fq = flatsAG n fq 1


-- almost certainly not as efficient as linesAG, because requires lineAG/closureAG call
-- among all pairs of distinct points, select those which are the first two in the line they generate
linesAG1 n fq = [ [x,y] | [x,y] <- combinationsOf 2 (ptsAG n fq),
                          [x,y] == take 2 (lineAG [x,y]) ]
-- the point of the condition at the end is to avoid listing the same line more than once

-- almost certainly not as efficient as linesAG, because requires lineAG/closureAG call
-- a line in AG(n,fq) is a translation (x) of a line through the origin (y)
linesAG2 n fq = [ [x,z] | x <- ptsAG n fq, y <- ptsPG (n-1) fq,
                          z <- [x <+> y], [x,z] == take 2 (closureAG [x,z]) ]


-- INCIDENCE GRAPH

-- |Incidence graph of PG(n,fq), considered as an incidence structure between points and lines
incidenceGraphPG :: (Num a, Ord a, FinSet a) => Int -> [a] -> Graph (Either [a] [[a]])
incidenceGraphPG n fq = G vs es where
    points = ptsPG n fq
    lines = linesPG n fq
    vs = L.sort $ map Left points ++ map Right lines
    es = L.sort [ [Left x, Right b] | b <- lines, x <- closurePG b]
-- Could also consider incidence structure between points and planes, etc

-- incidenceAuts (incidenceGraphPG n fq) == PGL(n,fq) * auts fq
-- For example, incidenceAuts (incidenceGraphPG 2 f4) =
-- PGL(3,f4) * auts f4
-- where PGL(3,f4)/PSL(3,f4) == f4* (multiplicative group of f4),
-- and auts f4 == { 1, x -> x^2 } (the Frobenius aut)
-- The full group is called PGammaL(3,f4)


-- |Incidence graph of AG(n,fq), considered as an incidence structure between points and lines
incidenceGraphAG :: (Num a, Ord a, FinSet a) => Int -> [a] -> Graph (Either [a] [[a]])
incidenceGraphAG n fq = G vs es where
    points = ptsAG n fq
    lines = linesAG n fq
    vs = L.sort $ map Left points ++ map Right lines
    es = L.sort [ [Left x, Right b] | b <- lines, x <- closureAG b]

-- incidenceAuts (incidenceGraphAG n fq) == Aff(n,fq) * auts fq
-- where Aff(n,fq), the affine group, is the semi-direct product GL(n,fq) * (fq^n)+
-- where (fq^n)+ is the additive group of translations
-- Each elt of Aff(n,fq) is of the form x -> ax + b, where a <- GL(n,fq), b <- (fq^n)+

orderGL n q = product [q^n - q^i | i <- [0..n-1] ]
-- for the first row, we can choose any vector except zero, hence q^n-1
-- for the second row, we can choose any vector except a multiple of the first, hence q^n-q
-- etc

orderAff n q = q^n * orderGL n q

orderPGL n q = orderGL n q `div` (q-1)

-- NOTE:
-- AG(n,F2) is degenerate:
-- Every pair of points is a line, so it is the complete graph on 2^n points
-- And as such has aut group S(2^n)


-- Heawood graph = incidence graph of Fano plane
heawood = to1n $ incidenceGraphPG 2 f2