{-
	Copyright (C) 2018 Dr. Alistair Ward

	This file is part of BishBosh.

	BishBosh is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	BishBosh is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with BishBosh.  If not, see <http://www.gnu.org/licenses/>.
-}
{- |
 [@AUTHOR@]	Dr. Alistair Ward

 [@DESCRIPTION@]	<https://en.wikipedia.org/wiki/ICCF_numeric_notation>.
-}

module BishBosh.Notation.ICCFNumeric(
-- * Types
-- ** Data-types
	ICCFNumeric(
--		MkICCFNumeric,
		getMove
--		getMaybePromotionRank
	),
-- * Constants
--	xOrigin,
--	yOrigin,
	origin,
	regexSyntax,
	toRank,
-- * Functions
--	encode,
	showsCoordinates,
-- ** Constructors
	mkICCFNumeric,
	mkICCFNumeric'
) where

import			Control.Arrow((&&&))
import qualified	BishBosh.Attribute.Rank		as Attribute.Rank
import qualified	BishBosh.Cartesian.Abscissa	as Cartesian.Abscissa
import qualified	BishBosh.Cartesian.Coordinates	as Cartesian.Coordinates
import qualified	BishBosh.Cartesian.Ordinate	as Cartesian.Ordinate
import qualified	BishBosh.Component.Move		as Component.Move
import qualified	BishBosh.Data.Exception		as Data.Exception
import qualified	Control.Arrow
import qualified	Control.Exception
import qualified	Data.Char
import qualified	Data.List.Extra
import qualified	Data.Maybe
import qualified	Data.Tuple

-- | The /x/-origin.
xOrigin :: Int
xOrigin :: Int
xOrigin	= Char -> Int
Data.Char.ord Char
'1'

-- | The /y/-origin.
yOrigin :: Int
yOrigin :: Int
yOrigin	= Char -> Int
Data.Char.ord Char
'1'

-- | The origin.
origin :: (Int, Int)
origin :: (Int, Int)
origin	= (Int
xOrigin, Int
yOrigin)

-- | Defines using a regex, the required syntax.
regexSyntax :: String
regexSyntax :: String
regexSyntax	= String
"[1-8]{4}[1-4]?"

-- | Constant translation from integral promotion-specifications to the corresponding /rank/.
toRank :: [(Int, Attribute.Rank.Rank)]
toRank :: [(Int, Rank)]
toRank	= [Int] -> [Rank] -> [(Int, Rank)]
forall a b. [a] -> [b] -> [(a, b)]
zip [Int
1 ..] [
	Rank
Attribute.Rank.Queen,
	Rank
Attribute.Rank.Rook,
	Rank
Attribute.Rank.Bishop,
	Rank
Attribute.Rank.Knight
 ]

-- | Defines a /move/, to enable i/o in /ICCF Numeric/-notation.
data ICCFNumeric x y	= MkICCFNumeric {
	ICCFNumeric x y -> Move x y
getMove			:: Component.Move.Move x y,
	ICCFNumeric x y -> Maybe Rank
getMaybePromotionRank	:: Maybe Attribute.Rank.Rank
} deriving ICCFNumeric x y -> ICCFNumeric x y -> Bool
(ICCFNumeric x y -> ICCFNumeric x y -> Bool)
-> (ICCFNumeric x y -> ICCFNumeric x y -> Bool)
-> Eq (ICCFNumeric x y)
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
forall x y.
(Eq x, Eq y) =>
ICCFNumeric x y -> ICCFNumeric x y -> Bool
/= :: ICCFNumeric x y -> ICCFNumeric x y -> Bool
$c/= :: forall x y.
(Eq x, Eq y) =>
ICCFNumeric x y -> ICCFNumeric x y -> Bool
== :: ICCFNumeric x y -> ICCFNumeric x y -> Bool
$c== :: forall x y.
(Eq x, Eq y) =>
ICCFNumeric x y -> ICCFNumeric x y -> Bool
Eq

-- | Smart constructor.
mkICCFNumeric :: Component.Move.Move x y -> Maybe Attribute.Rank.Rank -> ICCFNumeric x y
mkICCFNumeric :: Move x y -> Maybe Rank -> ICCFNumeric x y
mkICCFNumeric Move x y
move Maybe Rank
maybePromotionRank
	| Just Rank
rank	<- Maybe Rank
maybePromotionRank
	, Rank
rank Rank -> [Rank] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [Rank]
Attribute.Rank.promotionProspects	= Exception -> ICCFNumeric x y
forall a e. Exception e => e -> a
Control.Exception.throw (Exception -> ICCFNumeric x y)
-> (String -> Exception) -> String -> ICCFNumeric x y
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Exception
Data.Exception.mkInvalidDatum (String -> Exception) -> (String -> String) -> String -> Exception
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> String -> String
showString String
"BishBosh.Notation.ICCFNumeric.mkICCFNumeric:\tcan't promote to a " (String -> ICCFNumeric x y) -> String -> ICCFNumeric x y
forall a b. (a -> b) -> a -> b
$ Rank -> String -> String
forall a. Show a => a -> String -> String
shows Rank
rank String
"."
	| Bool
otherwise						= MkICCFNumeric :: forall x y. Move x y -> Maybe Rank -> ICCFNumeric x y
MkICCFNumeric {
		getMove :: Move x y
getMove			= Move x y
move,
		getMaybePromotionRank :: Maybe Rank
getMaybePromotionRank	= Maybe Rank
maybePromotionRank
	}

-- | Smart constructor.
mkICCFNumeric' :: Attribute.Rank.Promotable promotable => Component.Move.Move x y -> promotable -> ICCFNumeric x y
mkICCFNumeric' :: Move x y -> promotable -> ICCFNumeric x y
mkICCFNumeric' Move x y
move	= Move x y -> Maybe Rank -> ICCFNumeric x y
forall x y. Move x y -> Maybe Rank -> ICCFNumeric x y
mkICCFNumeric Move x y
move (Maybe Rank -> ICCFNumeric x y)
-> (promotable -> Maybe Rank) -> promotable -> ICCFNumeric x y
forall b c a. (b -> c) -> (a -> b) -> a -> c
. promotable -> Maybe Rank
forall a. Promotable a => a -> Maybe Rank
Attribute.Rank.getMaybePromotionRank

-- | Encodes the ordinate & abscissa.
encode :: (Enum x, Enum y) => Cartesian.Coordinates.Coordinates x y -> (ShowS, ShowS)
encode :: Coordinates x y -> (String -> String, String -> String)
encode	= Char -> String -> String
showChar (Char -> String -> String)
-> (Coordinates x y -> Char) -> Coordinates x y -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Char
Data.Char.chr (Int -> Char)
-> (Coordinates x y -> Int) -> Coordinates x y -> Char
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Int -> Int -> Int
forall a. Num a => a -> a -> a
+ (Int
xOrigin Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
Cartesian.Abscissa.xOrigin)) (Int -> Int) -> (Coordinates x y -> Int) -> Coordinates x y -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. x -> Int
forall a. Enum a => a -> Int
fromEnum (x -> Int) -> (Coordinates x y -> x) -> Coordinates x y -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Coordinates x y -> x
forall x y. Coordinates x y -> x
Cartesian.Coordinates.getX (Coordinates x y -> String -> String)
-> (Coordinates x y -> String -> String)
-> Coordinates x y
-> (String -> String, String -> String)
forall (a :: * -> * -> *) b c c'.
Arrow a =>
a b c -> a b c' -> a b (c, c')
&&& Char -> String -> String
showChar (Char -> String -> String)
-> (Coordinates x y -> Char) -> Coordinates x y -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Char
Data.Char.chr (Int -> Char)
-> (Coordinates x y -> Int) -> Coordinates x y -> Char
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Int -> Int -> Int
forall a. Num a => a -> a -> a
+ (Int
yOrigin Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
Cartesian.Ordinate.yOrigin)) (Int -> Int) -> (Coordinates x y -> Int) -> Coordinates x y -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. y -> Int
forall a. Enum a => a -> Int
fromEnum (y -> Int) -> (Coordinates x y -> y) -> Coordinates x y -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Coordinates x y -> y
forall x y. Coordinates x y -> y
Cartesian.Coordinates.getY

-- | Shows the specified /coordinates/.
showsCoordinates :: (Enum x, Enum y) => Cartesian.Coordinates.Coordinates x y -> ShowS
showsCoordinates :: Coordinates x y -> String -> String
showsCoordinates	= ((String -> String) -> (String -> String) -> String -> String)
-> (String -> String, String -> String) -> String -> String
forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry (String -> String) -> (String -> String) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
(.) ((String -> String, String -> String) -> String -> String)
-> (Coordinates x y -> (String -> String, String -> String))
-> Coordinates x y
-> String
-> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Coordinates x y -> (String -> String, String -> String)
forall x y.
(Enum x, Enum y) =>
Coordinates x y -> (String -> String, String -> String)
encode

instance (Enum x, Enum y) => Show (ICCFNumeric x y) where
	showsPrec :: Int -> ICCFNumeric x y -> String -> String
showsPrec Int
_ MkICCFNumeric {
		getMove :: forall x y. ICCFNumeric x y -> Move x y
getMove			= Move x y
move,
		getMaybePromotionRank :: forall x y. ICCFNumeric x y -> Maybe Rank
getMaybePromotionRank	= Maybe Rank
maybePromotionRank
	} = Coordinates x y -> String -> String
forall x y. (Enum x, Enum y) => Coordinates x y -> String -> String
showsCoordinates (
		Move x y -> Coordinates x y
forall x y. Move x y -> Coordinates x y
Component.Move.getSource Move x y
move
	 ) (String -> String) -> (String -> String) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Coordinates x y -> String -> String
forall x y. (Enum x, Enum y) => Coordinates x y -> String -> String
showsCoordinates (
		Move x y -> Coordinates x y
forall x y. Move x y -> Coordinates x y
Component.Move.getDestination Move x y
move
	 ) (String -> String) -> (String -> String) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String -> String)
-> (Rank -> String -> String) -> Maybe Rank -> String -> String
forall b a. b -> (a -> b) -> Maybe a -> b
Data.Maybe.maybe String -> String
forall a. a -> a
id (
		Int -> String -> String
forall a. Show a => a -> String -> String
shows (Int -> String -> String)
-> (Rank -> Int) -> Rank -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Maybe Int -> Int
forall a. HasCallStack => Maybe a -> a
Data.Maybe.fromJust (Maybe Int -> Int) -> (Rank -> Maybe Int) -> Rank -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Rank -> [(Rank, Int)] -> Maybe Int
forall a b. Eq a => a -> [(a, b)] -> Maybe b
`lookup` ((Int, Rank) -> (Rank, Int)) -> [(Int, Rank)] -> [(Rank, Int)]
forall a b. (a -> b) -> [a] -> [b]
map (Int, Rank) -> (Rank, Int)
forall a b. (a, b) -> (b, a)
Data.Tuple.swap [(Int, Rank)]
toRank)
	 ) Maybe Rank
maybePromotionRank

-- N.B. this merely validates the syntax, leaving any semantic errors to 'Model.Game.validate'.
instance (
	Enum	x,
	Enum	y,
	Ord	x,
	Ord	y
 ) => Read (ICCFNumeric x y) where
	readsPrec :: Int -> ReadS (ICCFNumeric x y)
readsPrec Int
_ String
s	= case String -> String
Data.List.Extra.trimStart String
s of
		Char
x : Char
y : Char
x' : Char
y' : String
remainder	-> let
			fromICCFNumeric :: Char -> Char -> Maybe (Coordinates x y)
fromICCFNumeric Char
x'' Char
y''	= x -> y -> Maybe (Coordinates x y)
forall x y.
(Enum x, Enum y, Ord x, Ord y) =>
x -> y -> Maybe (Coordinates x y)
Cartesian.Coordinates.mkMaybeCoordinates (
				Int -> x
forall a. Enum a => Int -> a
toEnum (Int -> x) -> Int -> x
forall a b. (a -> b) -> a -> b
$ Char -> Int
Data.Char.ord Char
x'' Int -> Int -> Int
forall a. Num a => a -> a -> a
+ (Int
Cartesian.Abscissa.xOrigin Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
xOrigin)
			 ) (
				Int -> y
forall a. Enum a => Int -> a
toEnum (Int -> y) -> Int -> y
forall a b. (a -> b) -> a -> b
$ Char -> Int
Data.Char.ord Char
y'' Int -> Int -> Int
forall a. Num a => a -> a -> a
+ (Int
Cartesian.Ordinate.yOrigin Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
yOrigin)
			 )
		 in [
			(Maybe Rank -> ICCFNumeric x y)
-> (Maybe Rank, String) -> (ICCFNumeric x y, String)
forall (a :: * -> * -> *) b c d.
Arrow a =>
a b c -> a (b, d) (c, d)
Control.Arrow.first (
				Move x y -> Maybe Rank -> ICCFNumeric x y
forall x y. Move x y -> Maybe Rank -> ICCFNumeric x y
mkICCFNumeric (Move x y -> Maybe Rank -> ICCFNumeric x y)
-> Move x y -> Maybe Rank -> ICCFNumeric x y
forall a b. (a -> b) -> a -> b
$ Coordinates x y -> Coordinates x y -> Move x y
forall x y.
(Eq x, Eq y) =>
Coordinates x y -> Coordinates x y -> Move x y
Component.Move.mkMove Coordinates x y
source Coordinates x y
destination
			) (
				case ReadS Int
forall a. Read a => ReadS a
reads ReadS Int -> ReadS Int
forall a b. (a -> b) -> a -> b
$ Int -> String -> String
forall a. Int -> [a] -> [a]
take Int
1 String
remainder of
					[(Int
digit, String
_)]
						| Just Rank
rank <- Int -> [(Int, Rank)] -> Maybe Rank
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup Int
digit [(Int, Rank)]
toRank	-> (Rank -> Maybe Rank
forall a. a -> Maybe a
Just Rank
rank, String -> String
forall a. [a] -> [a]
tail String
remainder)
						| Bool
otherwise				-> (Maybe Rank
forall a. Maybe a
Nothing, String
remainder)
					[(Int, String)]
_						-> (Maybe Rank
forall a. Maybe a
Nothing, String
remainder)
			) |
				Coordinates x y
source		<- Maybe (Coordinates x y) -> [Coordinates x y]
forall a. Maybe a -> [a]
Data.Maybe.maybeToList (Maybe (Coordinates x y) -> [Coordinates x y])
-> Maybe (Coordinates x y) -> [Coordinates x y]
forall a b. (a -> b) -> a -> b
$ Char -> Char -> Maybe (Coordinates x y)
forall x y.
(Enum x, Enum y, Ord x, Ord y) =>
Char -> Char -> Maybe (Coordinates x y)
fromICCFNumeric Char
x Char
y,
				Coordinates x y
destination	<- Maybe (Coordinates x y) -> [Coordinates x y]
forall a. Maybe a -> [a]
Data.Maybe.maybeToList (Maybe (Coordinates x y) -> [Coordinates x y])
-> Maybe (Coordinates x y) -> [Coordinates x y]
forall a b. (a -> b) -> a -> b
$ Char -> Char -> Maybe (Coordinates x y)
forall x y.
(Enum x, Enum y, Ord x, Ord y) =>
Char -> Char -> Maybe (Coordinates x y)
fromICCFNumeric Char
x' Char
y',
				Coordinates x y
source Coordinates x y -> Coordinates x y -> Bool
forall a. Eq a => a -> a -> Bool
/= Coordinates x y
destination
		 ] -- List-comprehension.
		String
_				-> []	-- No parse.

instance Attribute.Rank.Promotable (ICCFNumeric x y) where
	getMaybePromotionRank :: ICCFNumeric x y -> Maybe Rank
getMaybePromotionRank	= ICCFNumeric x y -> Maybe Rank
forall x y. ICCFNumeric x y -> Maybe Rank
getMaybePromotionRank