-- | We need to find (small) integer substitution such
-- that the denominator in our formula never vanishes.
--
-- That is, for @n@ we need to find @n@ integers @a[i]@ such that:
--
-- * @0  /=  a[i] -  a[j]@
--   
-- * @0  /=  a[i] - (a[j] + a[k])@
--
-- * @0  /=  (a[i] + a[j]) - (a[k] + a[l])@
--

{-# LANGUAGE ScopedTypeVariables, BangPatterns #-}
module Math.ThomPoly.Subs where

--------------------------------------------------------------------------------

import Data.Array
import Data.List

import Control.Monad
import System.Random
import System.IO.Unsafe as Unsafe

import Math.Combinat.Sets

import Math.Algebra.ModP

--------------------------------------------------------------------------------
-- * Lazily cached tables of substitutions

getSubs :: Int -> Array Int Integer
getSubs n = theSubsTable!!n

getSubsNum :: Num a => Int -> Array Int a
getSubsNum n = fmap fromInteger (theSubsTable!!n)

getSubsZp :: Int -> Array Int Zp
getSubsZp n = fmap mkZp (theSubsTable!!n)

-- | We cache a substitution table
theSubsTable :: [Array Int Integer]
theSubsTable = [ listArray (1,n) (Unsafe.unsafePerformIO (findSubs n)) | n <- [0..] ]

--------------------------------------------------------------------------------
-- * Find substitutions

-- | Select @k@ elements from a list in all possible orders
choosePerm :: Int -> [a] -> [[a]]
choosePerm n xs = concatMap Data.List.permutations (choose n xs)

-- | Checks if a substitution satisfies the constraints
{-# SPECIALIZE checkSubs :: [Int]     -> Bool #-}
{-# SPECIALIZE checkSubs :: [Integer] -> Bool #-}
checkSubs :: forall a. (Eq a, Num a) => [a] -> Bool
checkSubs input = ok2 && ok3 && ok4 where

  n   = length input
  nn  = [1..n] 
  arr = listArray (1,n) input :: Array Int a

  ok2 = and [ 0 /= (arr!i - arr!j)         | [i,j] <- choose 2 nn ] 

  ok3 = and [ 0 /= (arr!i - arr!j - arr!k) | [i,j,k] <- choosePerm 3 nn ] &&
        and [ 0 /= (arr!i - 2 * arr!j    ) | [i,j]   <- choosePerm 2 nn ]

  ok4 = and [ 0 /= (arr!i + arr!j - arr!k - arr!l) | [i,j,k,l] <- choosePerm 4 nn ] && 
        and [ 0 /= (arr!i + arr!j - 2 * arr!k    ) | [i,j,k]   <- choosePerm 3 nn ] 

--------------------------------------------------------------------------------

findSubsZp :: Int -> IO [Zp]
findSubsZp = liftM (map mkZp) . findSubs

-- | Find random substitution which satisfies the constraints
findSubs :: Int -> IO [Integer]
findSubs n = go 25 where

  go !bound = tryN 100 bound 

  tryN 0    !bound = go (div (bound*3) 2)
  tryN !cnt !bound = do
    subs <- replicateM n $ randomRIO (-bound,bound)
    case checkSubs subs of
      False -> tryN (cnt-1) bound
      True  -> do
        putStrLn $ "good substitution found! " ++ show subs
        return subs

--------------------------------------------------------------------------------