module Rubik.Solver.Optimal where

import Rubik.Cube
import Rubik.Solver
import Rubik.Tables.Moves
import Rubik.Tables.Distances
import Rubik.Tables.Internal

import qualified Data.Vector.Generic as G

{-# INLINE optiProj #-}
optiProj
  = fudsp |*| sfudsp |*| s sfudsp |*| co |*| sco |*| s sco |.| cp
  where
    fudsp = symProjFlipUDSlicePermu
    sfudsp = s fudsp
    co = rawProjection :: Projection' Move18 CornerOrien
    sco = s co
    cp = symProjCornerPermu
    s x = symmetricProj symmetry_urf3 x

{-# INLINE optiDist #-}
optiDist = maxDistance
  [ maxOrEqualPlusOne
      ( (\((,,,,,,) fudsp _ _ co _ _ _) -> (fudsp, co)) >$< fudsp_co
      , (\((,,,,,,) _ fudsp _ _ co _ _) -> (fudsp, co)) >$< fudsp_co
      , (\((,,,,,,) _ _ fudsp _ _ co _) -> (fudsp, co)) >$< fudsp_co
      )
  , (\((,,,,,,) _ _ _ co _ _ cp) -> (cp, co)) >$< cp_co
  ]

{-# INLINE maxOrEqualPlusOne #-}
maxOrEqualPlusOne (Distance f, Distance g, Distance h)
  = Distance $ \x -> let a = f x ; b = g x ; c = h x
    in if a == b && b == c && a /= 0 then a + 1
      else a `max` b `max` c

solve :: Cube -> Move
solve = solveWith move18Names moves optiProj optiDist
  where
    moves = (,,,,,,) m_fudsp m_fudsp m_fudsp m_co m_co m_co move18SymCornerPermu
    m_fudsp = move18SymFlipUDSlicePermu
    m_co = move18CornerOrien

{-# INLINE toIdx #-}
toIdx = uncurry $ indexWithSym invertedSym16CornerOrien (range ([] :: [CornerOrien]))

{-# INLINE fudsp_co #-}
fudsp_co = toIdx >$< Distance (fromIntegral . (dSym_CornerOrien_FlipUDSlicePermu G.!))
{-# INLINE cp_co #-}
cp_co = toIdx >$< Distance (dSym_CornerOrien_CornerPermu G.!)