{-# LANGUAGE OverloadedStrings #-}

-- TODO: factoring this interface
module Data.Stdf.WaferMap ( XyBin(..)
                          , stdfToXyBin
                          , xybsToGrid
                          , gridToString 
                          , stdfToWaferMapString 
                          , map2d
                          ) where

import Data.Stdf
import qualified Data.ByteString.Lazy as BL
import Data.Aeson
import Data.Maybe (fromJust)
import Data.List (sortBy, groupBy)
import Data.Function (on)
import Data.Ix (range)
import Data.List.Split (chunksOf)

data XyBin = XyBin { x :: Int
                   , y :: Int
                   , hbin :: Int }
           | Missing
    deriving (Show, Eq)

stdfToXyBin :: Stdf -> [XyBin]
stdfToXyBin (prr@(Prr { xCoord = Just xc
                      , yCoord = Just yc
                      , hardBin = hb }):prrs) =
    XyBin { x = fromIntegral xc
          , y = fromIntegral yc
          , hbin = fromIntegral hb } : stdfToXyBin prrs
stdfToXyBin (_:prrs) = stdfToXyBin prrs
stdfToXyBin [] = []

-- Take a list of XyBin sorted by x,y and a list of all x,y in the wafer map
-- Make a list of XyBin with Missing inserted where there wasn't an XyBin for
-- a given x,y
addmissing :: [XyBin] -> [(Int, Int)] -> [XyBin]
addmissing [] []      = []
addmissing [] xys     = replicate (length xys) Missing
addmissing (d:ds) (xy:xys)
    | x d == fst xy && y d == snd xy = d : addmissing ds xys
    | otherwise                      = Missing : addmissing (d:ds) xys


binToStr :: Maybe Int -> String
binToStr Nothing = "."
binToStr (Just bn) = show bn

-- sort by y,x
-- groupby y,x
-- take lowest hbin # from groups
-- use min max x y to insert Missing dies
xybsToGrid :: [XyBin] -> [[XyBin]]
xybsToGrid xybs = mkgrid cols gridlist
      where ordXy a b = case compare (y a) (y b) of
                            EQ -> compare (x a) (x b)
                            ordy -> ordy
            eqXy a b = x a == x b && y a == y b
            ordBin a b = compare (hbin a) (hbin b)
            waferxy =  [(atx, aty) | aty <- range (miny, maxy)
                                   , atx <- range (minx, maxx)]
            sortedxy = sortBy ordXy xybs
            groupedxy = groupBy eqXy sortedxy
            sortedbin = map (sortBy ordBin) groupedxy
            dropedextra = map head sortedbin
            xs = map x dropedextra
            ys = map y dropedextra
            minx = minimum xs
            maxx = maximum xs
            miny = minimum ys
            maxy = maximum ys
            gridlist = addmissing dropedextra waferxy
            cols = maxx - minx + 1

mkgrid :: Int -> [a] -> [[a]]
mkgrid = chunksOf

-- take list of rows
-- convert to rows with columns of show bin
-- pad to maximum show bin width
-- print
gridToString :: [[XyBin]] -> String
gridToString xybs = unlines $ map unwords padded
    where
        pad w s = s ++ replicate (w - length s) ' '

        binOrNothing Missing = Nothing
        binOrNothing XyBin { hbin = hbin } = Just hbin

        bins = map2d binOrNothing xybs -- start here
        binstrs :: [[String]]
        binstrs = map2d binToStr bins
        maxwidth :: Int
        maxwidth = maximum $ (concatMap . map) length binstrs
        padded = map2d (pad maxwidth) binstrs
        cols = length $ head xybs
        rows = length xybs

map2d :: (a -> b) -> [[a]] -> [[b]]
map2d = map . map

stdfToWaferMapString :: Stdf -> String
stdfToWaferMapString bodies = let xybs = stdfToXyBin bodies
                                  grid = xybsToGrid xybs
                              in gridToString grid