module Codec.Binary.QRCode.GaloisField where
import Data.Bits
import Numeric
import Data.Char
import qualified Data.Map as M
import qualified Data.Vector.Unboxed as VU
qrGFSize :: Int
qrGFSize = 256
qrFieldPolynomial :: Int
qrFieldPolynomial = 285
readBin :: String -> Int
readBin s = go 0 0 (reverse s)
where
go :: Int -> Int -> String -> Int
go acc _ "" = acc
go acc pow ('0':xs) = go acc (pow+1) xs
go acc pow ('1':xs) = go (acc + 2 ^ pow) (pow+1) xs
go _ _ xs = error $ "Malformed binary string '" ++ xs ++ "'"
newtype GFElement = GFElement { gfGet :: Int } deriving (Eq)
newtype GFPolynomial = GFPolynomial { gfpGetTerms :: [GFElement] } deriving (Show, Eq)
type GFPTerm = (GFElement, Int)
instance Show GFElement where
show (GFElement x) = "GFE " ++ show x
gfAdd :: GFElement -> GFElement -> GFElement
gfAdd (GFElement x) (GFElement y) = GFElement (x `xor` y)
gfMinus :: GFElement -> GFElement -> GFElement
gfMinus = gfAdd
gfALogs :: [Int]
gfALogs = 1:(map f gfALogs)
where
f x = let x' = x * 2 in
if x' >= qrGFSize
then x' `xor` qrFieldPolynomial
else x'
gfALogsVec :: VU.Vector Int
gfALogsVec = VU.fromList . take 255 $ gfALogs
gfALog :: Int -> Int
gfALog = (gfALogsVec VU.!) . (`mod` 255)
gfLogs :: M.Map Int Int
gfLogs = M.fromList $ take qrGFSize $ zip gfALogs [0..]
gfLog :: Int -> Int
gfLog = (M.!) gfLogs
gfMult :: GFElement -> GFElement -> GFElement
gfMult z@(GFElement 0) _ = z
gfMult _ z@(GFElement 0) = z
gfMult x (GFElement 1) = x
gfMult (GFElement 1) x = x
gfMult (GFElement x) (GFElement y) =
GFElement $ gfALog(gfLog x + gfLog y) `mod` qrGFSize
gfQuot :: GFElement -> GFElement -> GFElement
gfQuot x (GFElement 1) = x
gfQuot _ (GFElement 0) = error "div by zero"
gfQuot (GFElement x) (GFElement y) =
GFElement $ gfALog(gfLog x gfLog y + (qrGFSize1)) `mod` (qrGFSize1)
instance Num GFElement where
(+) = gfAdd
() = gfMinus
(*) = gfMult
negate = id
abs = id
signum = const $ GFElement 1
fromInteger = GFElement . fromInteger
gfShowBin :: GFElement -> String
gfShowBin (GFElement n) = replicate padLength '0' ++ str
where str = showIntAtBase 2 intToDigit n ""
padLength = 8 length str
gfQuotRem :: GFElement -> GFElement -> (GFElement, GFElement)
gfQuotRem x y = let q = gfQuot x y in (q, x q)
gfpOrder :: GFPolynomial -> Int
gfpOrder (GFPolynomial terms) = length terms 1
gfZeroes :: [GFElement]
gfZeroes = map GFElement (repeat 0);
gfpEnlarge :: Int -> GFPolynomial -> GFPolynomial
gfpEnlarge n p@(GFPolynomial terms)
| order >= n = p
| otherwise = GFPolynomial $ take (norder) gfZeroes ++ terms
where order = gfpOrder p
gfpShowBin :: GFPolynomial -> String
gfpShowBin (GFPolynomial xs) = concatMap gfShowBin xs
gfpHead :: GFPolynomial -> GFElement
gfpHead = head . gfpGetTerms
gfpZipWith :: (GFElement -> GFElement -> GFElement) -> GFPolynomial -> GFPolynomial -> GFPolynomial
gfpZipWith f a b = GFPolynomial $ dropWhile (== GFElement 0) $ zipWith f aTerms bTerms
where
maxOrder = max (gfpOrder a) (gfpOrder b)
(GFPolynomial aTerms) = gfpEnlarge maxOrder a
(GFPolynomial bTerms) = gfpEnlarge maxOrder b
gfpAdd :: GFPolynomial -> GFPolynomial -> GFPolynomial
gfpAdd = gfpZipWith (+)
gfpMinus :: GFPolynomial -> GFPolynomial -> GFPolynomial
gfpMinus = gfpZipWith ()
gfpMultTerm :: GFPolynomial -> GFPTerm -> GFPolynomial
gfpMultTerm (GFPolynomial terms) (coefficient,order) =
GFPolynomial $ map (*coefficient) (terms ++ take order gfZeroes)
gfpAddTerm :: GFPolynomial -> GFPTerm -> GFPolynomial
gfpAddTerm p (coefficient, order) = gfpZipWith (+) p additive
where
additive = GFPolynomial $ coefficient : take order gfZeroes
gfpQuotRem :: GFPolynomial -> GFPolynomial -> (GFPolynomial, GFPolynomial)
gfpQuotRem dividend divisor = go dividend (GFPolynomial [])
where
divHead = gfpHead divisor
go currentDividend q
| order < 0 = (q, currentDividend)
| gfpOrder nextDividend == 0 = (q', (GFPolynomial []))
| otherwise =
go nextDividend (q |+| currentTerm)
where
nextDividend = currentDividend |-| currentQuotient
q' = q |+| currentTerm
coefficient = gfQuot (gfpHead currentDividend) divHead
order = gfpOrder currentDividend gfpOrder divisor
currentTerm = (coefficient,order)
currentQuotient = divisor |*| currentTerm
(|-|) = gfpMinus
(|*|) = gfpMultTerm
(|+|) = gfpAddTerm
gfpRightPad :: Int -> GFPolynomial -> GFPolynomial
gfpRightPad n (GFPolynomial terms) = GFPolynomial $ terms ++ replicate n 0
mkPolynomial :: [Int] -> GFPolynomial
mkPolynomial = GFPolynomial . map GFElement
gfpToBinaryRepr :: GFPolynomial -> Int
gfpToBinaryRepr (GFPolynomial terms) = readBin bits
where bits = concatMap (show . gfGet) terms