{-# LANGUAGE DeriveDataTypeable #-}

-- |
-- Description : Generate Unique Strings
--
-- This module generates short non-empty unique printable strings (IE without
-- funny characters).  Quotes and backslashes are not included, so printing
-- should not be too hard.  Periods are also not included, for the
-- benefit of NewNames.hs.
module Util.UniqueString(
   UniqueStringSource, -- A source of unique strings.  Instance of Typeable
   newUniqueStringSource, -- :: IO UniqueStringSource
   newUniqueString, -- :: UniqueStringSource -> IO String


   maxUniqueStringSources, -- :: [UniqueStringSource] -> IO UniqueStringSource

   -- Here is a "pure" interface.
   UniqueStringCounter,

   firstUniqueStringCounter, -- :: UniqueStringCounter
      -- This is what you start with
   stepUniqueStringCounter, -- :: UniqueStringCounter
      -- -> (String,UniqueStringCounter)
      -- and this is how you get a new String out.


   -- read/createUniqueStringSource are used by types/CodedValue
   -- to import and export string sources.
   readUniqueStringSource, -- :: UniqueStringSource -> IO [Int]
   createUniqueStringSource, -- :: [Int] -> IO UniqueStringSource

   -- Create non-conflicting string which cannot be produced by
   -- newUniqueString.  This is useful for exceptional cases.
   newNonUnique, -- :: String -> String

   -- The first string generated by newUniqueString or stepUniqueStringCounter
   firstUniqueString, -- :: String
   ) where

import Data.Array

import Control.Concurrent

import Util.ExtendedPrelude
import Util.Dynamics

-- The list of "printable" characters that may occur in one of these
-- strings.
--
-- 20.9.02.  {} characters eliminated because daVinci doesn't like them.
printableCharsStr :: String
printableCharsStr =
   "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()"
   ++ "-_+=|~[];:,<>/?"

-- The same, as an array and length.
printableCharsLen :: Int
printableCharsLen = length printableCharsStr

printableCharsArr :: Array Int Char
printableCharsArr = listArray (0,printableCharsLen-1) printableCharsStr

-- -------------------------------------------------------------------
-- The impure interface.
-- -------------------------------------------------------------------

newtype UniqueStringSource = UniqueStringSource (MVar UniqueStringCounter)
   deriving (Typeable)

newUniqueStringSource :: IO UniqueStringSource
newUniqueStringSource =
   do
      mVar <- newMVar firstUniqueStringCounter
      return (UniqueStringSource mVar)

newUniqueString :: UniqueStringSource -> IO String
newUniqueString (UniqueStringSource mVar) =
   do
      uniqueStringCounter <- takeMVar mVar
      let
         (str,nextUniqueStringCounter) =
            stepUniqueStringCounter uniqueStringCounter
      putMVar mVar nextUniqueStringCounter
      return str

-- | readUniqueStringSource is used by types\/CodedValue.hs to export values.
readUniqueStringSource :: UniqueStringSource -> IO [Int]
readUniqueStringSource (UniqueStringSource mVar) =
   do
      (UniqueStringCounter l) <- readMVar mVar
      return l

-- | createUniqueStringSource is the inverse of readUniqueStringSource.
createUniqueStringSource :: [Int] -> IO UniqueStringSource
createUniqueStringSource l =
   do
      mVar <- newMVar (UniqueStringCounter l)
      return (UniqueStringSource mVar)


{- unused
compareUniqueStringSource :: UniqueStringSource -> UniqueStringSource
   -> IO Ordering
compareUniqueStringSource (UniqueStringSource mVar1) (UniqueStringSource mVar2)
      =
   do
      c1 <- readMVar mVar1
      c2 <- readMVar mVar2
      return (compare c1 c2)
-}

maxUniqueStringSources :: [UniqueStringSource] -> IO UniqueStringSource
maxUniqueStringSources stringSources =
   do
      stringCounters <- mapM
         (\ (UniqueStringSource mVar) -> readMVar mVar)
         stringSources
      let
         maxCounter = foldl max firstUniqueStringCounter stringCounters
      mVar <- newMVar maxCounter
      return (UniqueStringSource mVar)

-- -------------------------------------------------------------------
-- The pure interface.
-- -------------------------------------------------------------------


-- UniqueStringCounter is a list of numbers from 0 to printableCharsLen-1.
-- The last number is at least 1.
newtype UniqueStringCounter = UniqueStringCounter [Int]

firstUniqueStringCounter :: UniqueStringCounter
firstUniqueStringCounter = UniqueStringCounter [0]

stepUniqueStringCounter :: UniqueStringCounter -> (String,UniqueStringCounter)
stepUniqueStringCounter (uniqueStringCounter @ (UniqueStringCounter ilist)) =
      (toStringUniqueStringCounter uniqueStringCounter,
         UniqueStringCounter (step ilist))
   where
      step [] = [1]
      step (first:rest) =
         if first == printableCharsLen -1
            then
               0:step rest
            else
               (first+1):rest

toStringUniqueStringCounter :: UniqueStringCounter -> String
toStringUniqueStringCounter (UniqueStringCounter ilist) =
   map (\ i -> printableCharsArr ! i) ilist

instance Eq UniqueStringCounter where
   (==) = mapEq (\ (UniqueStringCounter l) -> l)

instance Ord UniqueStringCounter where
   compare (UniqueStringCounter l1) (UniqueStringCounter l2)
         = comp l1 l2
      where
         comp [] [] = EQ
         comp (_:_) [] = GT
         comp [] (_:_) = LT
         comp (c1:cs1) (c2:cs2) = case comp cs1 cs2 of
            EQ -> compare c1 c2
            other -> other

-- -------------------------------------------------------------------
-- firstUniqueString
-- -------------------------------------------------------------------

firstUniqueString :: String
firstUniqueString =
   let
      (s,_) = stepUniqueStringCounter firstUniqueStringCounter
   in
      s

-- -------------------------------------------------------------------
-- newNonUnique
-- -------------------------------------------------------------------

-- | Create non-conflicting string which cannot be produced by
-- newUniqueString.  This is useful for exceptional cases.
-- We add this by adding a character with integer value 0 at the end.
newNonUnique :: String -> String
newNonUnique str = str ++ [printableCharsArr ! 0]