-----------------------------------------------------------------------------
-- |
-- Module      :  ToySolver.MaxCut
-- Copyright   :  (c) Masahiro Sakai 2018
-- License     :  BSD-style
--
-- Maintainer  :  masahiro.sakai@gmail.com
-- Stability   :  provisional
-- Portability :  portable
--
-----------------------------------------------------------------------------
module ToySolver.MaxCut
  ( Problem (..)
  , fromEdges
  , edges
  , buildDSDPMaxCutGraph
  , buildDSDPMaxCutGraph'
  , Solution
  , eval
  , evalEdge
  ) where

import Data.Array.Unboxed
import Data.ByteString.Builder
import Data.ByteString.Builder.Scientific
import qualified Data.ByteString.Lazy.Char8 as BL
import qualified Data.Foldable as F
import Data.IntMap.Strict (IntMap)
import qualified Data.IntMap.Strict as IntMap
import Data.Monoid
import Data.Scientific (Scientific)

data Problem a
  = Problem
  { numNodes :: !Int
    -- ^ Number of nodes N. Nodes are numbered from 0 to N-1.
  , numEdges :: !Int
    -- ^ Number of edges.
  , matrix :: IntMap (IntMap a)
    -- ^ Non-zero entries of symmetric weight matrix
  } deriving (Eq, Ord, Show)

instance Functor Problem where
  fmap f Problem{ numNodes = n, numEdges = m, matrix = mat } =
    Problem{ numNodes = n, numEdges = m, matrix = fmap (fmap f) mat }

fromEdges :: Num a => Int -> [(Int,Int,a)] -> Problem a
fromEdges n es = Problem n (length es) $ IntMap.unionsWith (IntMap.unionWith (+)) $
  [IntMap.fromList [(v1, IntMap.singleton v2 w), (v2, IntMap.singleton v1 w)] | (v1,v2,w) <- es]

edges :: Problem a -> [(Int,Int,a)]
edges prob = do
  (a,m) <- IntMap.toList $ matrix prob
  (b,w) <- IntMap.toList $ snd $ IntMap.split a m
  return (a,b,w)

buildDSDPMaxCutGraph :: Problem Scientific -> Builder
buildDSDPMaxCutGraph = buildDSDPMaxCutGraph' scientificBuilder

buildDSDPMaxCutGraph' :: (a -> Builder) -> Problem a -> Builder
buildDSDPMaxCutGraph' weightBuilder prob = header <> body
  where
    header = intDec (numNodes prob) <> char7 ' ' <> intDec (numEdges prob) <> char7 '\n'
    body = mconcat $ do
      (a,b,w) <- edges prob
      return $ intDec (a+1) <> char7 ' ' <> intDec (b+1) <> char7 ' ' <> weightBuilder w <> char7 '\n'

type Solution = UArray Int Bool

eval :: Num a => Solution -> Problem a -> a
eval sol prob = sum [w | (a,b,w) <- edges prob, sol ! a /= sol ! b]

evalEdge :: Num a => Solution -> (Int,Int,a) -> a
evalEdge sol (a,b,w)
  | sol ! a /= sol ! b = w
  | otherwise = 0