module Kewar.Encoding.Data (encodeData, toBitString) where

import Data.Char (ord)
import Kewar.Constants (alphaNumericValue, characterCountIndicator, modeIndicator, totalBits, groupsCodeWords)
import Kewar.Types (BitString, CorrectionLevel, Mode (..), Version, Group)
import Utils (chunksOf, leftPad, leftUnpad, readInt, toBin)
import Data.Foldable (foldl')

-- | Encodes an input string to a BitString with length as per Kewar specification
encodeData :: String -> Mode -> Version -> CorrectionLevel -> [Group]
encodeData :: String -> Mode -> Version -> CorrectionLevel -> [Group]
encodeData String
i Mode
m Version
v CorrectionLevel
cl = String -> Version -> CorrectionLevel -> [Group]
groups (String
byteString String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> Version -> String
padBytes String
byteString Version
requiredBits) Version
v CorrectionLevel
cl
  where
    requiredBits :: Version
requiredBits = Version -> CorrectionLevel -> Version
totalBits Version
v CorrectionLevel
cl
    encoded :: String
encoded = String -> Mode -> Version -> String
basicEncodeData String
i Mode
m Version
v
    byteString :: String
byteString = String -> String
toByteString (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ String
encoded String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> Version -> String
terminator String
encoded Version
requiredBits

-- Convert Input to BitString
byteToBitString :: String -> BitString
byteToBitString :: String -> String
byteToBitString = (Char -> String) -> String -> String
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (Version -> Char -> String -> String
leftPad Version
8 Char
'0' (String -> String) -> (Char -> String) -> Char -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Version -> String
toBin (Version -> String) -> (Char -> Version) -> Char -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> Version
ord))

alphaNumericToBitString :: String -> BitString
alphaNumericToBitString :: String -> String
alphaNumericToBitString String
i = do
  let sums :: [Version]
sums = ([Version] -> Version) -> [[Version]] -> [Version]
forall a b. (a -> b) -> [a] -> [b]
map (\[Version]
j -> if [Version] -> Version
forall (t :: * -> *) a. Foldable t => t a -> Version
length [Version]
j Version -> Version -> Bool
forall a. Eq a => a -> a -> Bool
== Version
2 then ([Version] -> Version
forall a. [a] -> a
head [Version]
j Version -> Version -> Version
forall a. Num a => a -> a -> a
* Version
45) Version -> Version -> Version
forall a. Num a => a -> a -> a
+ [Version] -> Version
forall a. [a] -> a
last [Version]
j else [Version] -> Version
forall a. [a] -> a
head [Version]
j) (Version -> [Version] -> [[Version]]
forall a. Version -> [a] -> [[a]]
chunksOf Version
2 ([Version] -> [[Version]]) -> [Version] -> [[Version]]
forall a b. (a -> b) -> a -> b
$ (Char -> Version) -> String -> [Version]
forall a b. (a -> b) -> [a] -> [b]
map Char -> Version
alphaNumericValue String
i)
  let initial :: String
initial = (Version -> String) -> [Version] -> String
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (Version -> Char -> String -> String
leftPad Version
11 Char
'0' (String -> String) -> (Version -> String) -> Version -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Version -> String
toBin) ([Version] -> [Version]
forall a. [a] -> [a]
init [Version]
sums)
  let finalPad :: Version
finalPad = if Version -> Bool
forall a. Integral a => a -> Bool
odd (Version -> Bool) -> Version -> Bool
forall a b. (a -> b) -> a -> b
$ String -> Version
forall (t :: * -> *) a. Foldable t => t a -> Version
length String
i then Version
6 else Version
11
  let final :: String
final = Version -> Char -> String -> String
leftPad Version
finalPad Char
'0' (Version -> String
toBin ([Version] -> Version
forall a. [a] -> a
last [Version]
sums))
  String
initial String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
final

numericToBitString :: String -> BitString
numericToBitString :: String -> String
numericToBitString String
i = (String -> String) -> [String] -> String
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (String -> String
step (String -> String) -> (String -> String) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> String -> String
leftUnpad Char
'0') (Version -> String -> [String]
forall a. Version -> [a] -> [[a]]
chunksOf Version
3 String
i)
  where
    transform :: String -> String
transform = Version -> String
toBin (Version -> String) -> (String -> Version) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Version
readInt
    step :: String -> String
step String
chunk
      | String -> Version
forall (t :: * -> *) a. Foldable t => t a -> Version
length String
chunk Version -> Version -> Bool
forall a. Eq a => a -> a -> Bool
== Version
1 = Version -> Char -> String -> String
leftPad Version
4 Char
'0' (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ String -> String
transform String
chunk
      | String -> Version
forall (t :: * -> *) a. Foldable t => t a -> Version
length String
chunk Version -> Version -> Bool
forall a. Eq a => a -> a -> Bool
== Version
2 = Version -> Char -> String -> String
leftPad Version
7 Char
'0' (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ String -> String
transform String
chunk
      | Bool
otherwise = Version -> Char -> String -> String
leftPad Version
10 Char
'0' (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ String -> String
transform String
chunk

toBitString :: Mode -> String -> BitString
toBitString :: Mode -> String -> String
toBitString Mode
Numeric String
i = String -> String
numericToBitString String
i
toBitString Mode
AlphaNumeric String
i = String -> String
alphaNumericToBitString String
i
toBitString Mode
Byte String
i = String -> String
byteToBitString String
i

-- | Converts input to BitString and chains it with mode indicator and character count indicator
basicEncodeData :: String -> Mode -> Version -> BitString
basicEncodeData :: String -> Mode -> Version -> String
basicEncodeData String
i Mode
m Version
v = Mode -> String
modeIndicator Mode
m String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> Mode -> Version -> String
characterCountIndicator String
i Mode
m Version
v String -> String -> String
forall a. [a] -> [a] -> [a]
++ Mode -> String -> String
toBitString Mode
m String
i

-- | Takes a BitString and ensures its length is multiple of 8 by adding 0s
toByteString :: BitString -> BitString
toByteString :: String -> String
toByteString String
s
  | Version
rest Version -> Version -> Bool
forall a. Eq a => a -> a -> Bool
== Version
0 = String
s
  | Bool
otherwise = String
s String -> String -> String
forall a. [a] -> [a] -> [a]
++ Version -> Char -> String
forall a. Version -> a -> [a]
replicate (Version
8 Version -> Version -> Version
forall a. Num a => a -> a -> a
- Version
rest) Char
'0'
  where
    rest :: Version
rest = String -> Version
forall (t :: * -> *) a. Foldable t => t a -> Version
length String
s Version -> Version -> Version
forall a. Integral a => a -> a -> a
`mod` Version
8

-- | Return as many '0' as needed to fill required length.
-- Terminator string cannot be longer than 4 chars
terminator :: BitString -> Int -> BitString
terminator :: String -> Version -> String
terminator String
s Version
requiredBits
  | Version
delta Version -> Version -> Bool
forall a. Ord a => a -> a -> Bool
>= Version
4 = String
"0000"
  | Bool
otherwise = Version -> Char -> String
forall a. Version -> a -> [a]
replicate Version
delta Char
'0'
  where
    delta :: Version
delta = Version
requiredBits Version -> Version -> Version
forall a. Num a => a -> a -> a
- String -> Version
forall (t :: * -> *) a. Foldable t => t a -> Version
length String
s

-- | Add alternating sequence of pad bytes to fill the string
padBytes :: BitString -> Int -> BitString
padBytes :: String -> Version -> String
padBytes String
s Version
requiredBits = do
  let numberOfPadBytes :: Version
numberOfPadBytes = (Version
requiredBits Version -> Version -> Version
forall a. Num a => a -> a -> a
- String -> Version
forall (t :: * -> *) a. Foldable t => t a -> Version
length String
s) Version -> Version -> Version
forall a. Integral a => a -> a -> a
`div` Version
8
  (Version -> String) -> [Version] -> String
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (\Version
i -> if Version -> Bool
forall a. Integral a => a -> Bool
odd Version
i then String
"11101100" else String
"00010001") [Version
1 .. Version
numberOfPadBytes]

-- | Returns a list of groups of blocks for a given bitstring
groups :: BitString -> Version -> CorrectionLevel -> [Group]
groups :: String -> Version -> CorrectionLevel -> [Group]
groups String
input Version
version CorrectionLevel
correctionLevel = do
  let gcw :: [(Version, Version)]
gcw = Version -> CorrectionLevel -> [(Version, Version)]
groupsCodeWords Version
version CorrectionLevel
correctionLevel
  let dataCodewords :: [String]
dataCodewords = Version -> String -> [String]
forall a. Version -> [a] -> [[a]]
chunksOf Version
8 String
input :: [BitString]
  ([Group], [String]) -> [Group]
forall a b. (a, b) -> a
fst (([Group], [String]) -> [Group]) -> ([Group], [String]) -> [Group]
forall a b. (a -> b) -> a -> b
$ (([Group], [String]) -> (Version, Version) -> ([Group], [String]))
-> ([Group], [String])
-> [(Version, Version)]
-> ([Group], [String])
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' (\([Group]
acc, [String]
cw) (Version
gs, Version
size) -> ([Group]
acc [Group] -> [Group] -> [Group]
forall a. [a] -> [a] -> [a]
++ [Version -> Group -> Group
forall a. Version -> [a] -> [a]
take Version
gs (Version -> [String] -> Group
forall a. Version -> [a] -> [[a]]
chunksOf Version
size [String]
cw)], Version -> [String] -> [String]
forall a. Version -> [a] -> [a]
drop (Version
gs Version -> Version -> Version
forall a. Num a => a -> a -> a
* Version
size) [String]
cw)) ([], [String]
dataCodewords) [(Version, Version)]
gcw