{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, GADTs, FlexibleInstances, FlexibleContexts, ScopedTypeVariables, TypeSynonymInstances, StandaloneDeriving, DeriveDataTypeable, EmptyDataDecls, NamedFieldPuns, CPP #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

module Control.Etage.Internals (
  Axon(..),
  Nerve(..),
  Impulse(..),
  LiveNeuron(..),
  ImpulseValue,
  ImpulseTime,
  AxonConductive,
  AxonNonConductive,
  FromNerve(..),
  ForNerve(..),
  BothNerve(..),
  NeuronDissolved,
  NeuronId,
  waitForException,
  GrowAxon(..),
  growNerve,
  cross
) where

import Control.Applicative
import Control.Concurrent hiding (Chan, newChan)
import Control.Concurrent.MSampleVar
import Data.Data
import Data.Time.Clock.POSIX
import Numeric
import Text.ParserCombinators.ReadP

import Control.Etage.Chan

-- TODO: Find better general representation for values (something analog to what a hologram is, so that it can be gradually simplified and gradually reconstructed). Could be an Incubation program itself?
-- | Type of a general representation of 'Impulse' values (data payload). Currently it is just a list of 'Rational' values.
type ImpulseValue = [Rational]

-- | Type of 'Impulse' timestamp. You can use 'getCurrentImpulseTime' for timestamp representing current time.
type ImpulseTime = POSIXTime

instance Read ImpulseTime where
  readsPrec _ r = do
    (time, sec) <- readFloat r
    ('s', rest) <- readP_to_S (char 's') sec
    return (time, rest)

{-|
Type class with common methods for impulses send over 'Nerve's and processed in 'Neuron's so that it is possible to define
'Neuron's which operate on any 'Impulse' type by using 'AnyImpulse' type as their receiving 'Impulse's type. An example of
such 'Neuron' is "Control.Etage.Dump".
-}
class (Show i, Typeable i) => Impulse i where
  -- | This method should return a timestamp when the 'Impulse' was created/finalized what should be the moment just before it is send
  -- over the 'Nerve', the moment it formed into its final form and started leaving the 'Neuron'. As Haskell is a lazy language this
  -- does not mean that at that moment all values the 'Impulse' defines are really already evaluated (they are evaluated when they are
  -- needed, probably in some other 'Neuron').
  --
  -- You can do something like:
  --
  -- > time <- getCurrentImpulseTime
  -- > sendFromNeuron nerve YourImpulse { impulseTimestamp = time, ... }
  impulseTime :: i -> ImpulseTime
  -- | This method should return all values (data payload) the 'Impulse' defines. Currently order and format is not yet finalized so
  -- it is just a list of 'Rational' values in some order (for now it probably should be the order in which the values are defined
  -- in the 'Impulse' constructor).
  --
  -- It is meant to allow general 'Neurons' which can work on any 'Impulse' type to be developed. For example 'Neuron's which
  -- implement some machine learning or data mining algorithms. It is on purpose that values are cleared of any semantic
  -- meaning so algorithms have better chance not to get in touch with some unintended domain specific knowledge.
  impulseValue :: i -> ImpulseValue

{-|
Is axon (one direction of a 'Nerve') conductive? Yes, it is.

This is type checked and enforced. If you define axon as conductive you have to make make sure that 'Impulse's send along it are
really read somewhere, otherwise a memory leak will occur.
-}
data AxonConductive deriving (Typeable)

deriving instance Data AxonConductive

{-|
Is axon (one direction of a 'Nerve') conductive? No, it is not.

This is type checked and enforced. It is useful to specify nonconductive axons when you are not interested in 'Impulse's from a
particular axon (direction), making sure there will not be a memory leak because 'Impulse's would pile up.
-}
data AxonNonConductive deriving (Typeable)

deriving instance Data AxonNonConductive

data Axon impulse conductivity where
  Axon :: Impulse i => Chan i -> Axon i AxonConductive
  NoAxon :: Axon i AxonNonConductive

{-|
Type representing a 'Nerve' between 'Neuron's. It is bi-directional (from and to a 'Neuron', each direction being one axon) and you
can specify type of 'Impulse's traveling along the axon and its conductivity (with AxonConductive or
AxonNonConductive).

You mostly do not need to specify this type manually if you are using 'growNeuron' and one of 'NerveBoth', 'NerveNone',
'NerveOnlyFrom' and 'NerveOnlyFor' types.
-}
data Nerve from fromConductivity for forConductivity where
  Nerve :: (Impulse from, Impulse for) => Axon from fromConductivity -> Axon for forConductivity -> Nerve from fromConductivity for forConductivity

deriving instance Typeable4 Nerve

instance (Typeable forConductivity, Typeable fromConductivity, Typeable from, Typeable for) => Show (Nerve from fromConductivity for forConductivity) where
  show = show . typeOf

{-|
An existentially quantified type encompassing all 'Nerve's which are conductive from a 'Neuron'.
-}
data FromNerve where
  FromNerve :: Impulse from => Nerve from AxonConductive for forConductivity -> FromNerve

{-|
An existentially quantified type encompassing all 'Nerve's which are conductive to a 'Neuron'.
-}
data ForNerve where
  ForNerve :: Impulse for => Nerve from fromConductivity for AxonConductive -> ForNerve

{-|
An existentially quantified type encompassing all 'Nerve's which are conductive in both directions.
-}
data BothNerve where
  BothNerve :: (Impulse from, Impulse for) => Nerve from AxonConductive for AxonConductive -> BothNerve

type NeuronDissolved = MSampleVar ()
type NeuronId = ThreadId

#if !(MIN_VERSION_base(4,4,0))
deriving instance Typeable1 MSampleVar
#endif

instance Show NeuronDissolved where
  show = show . typeOf

{-|
Type representing a 'live' 'Neuron'.
-}
data LiveNeuron = LiveNeuron NeuronDissolved NeuronId deriving (Eq, Typeable)

instance Show LiveNeuron where
  show = show . typeOf

waitForException :: IO a
waitForException = newEmptyMVar >>= takeMVar

class GrowAxon a where
  growAxon :: IO a

instance Impulse i => GrowAxon (Axon i AxonConductive) where
  growAxon = Axon <$> newChan

instance GrowAxon (Axon i AxonNonConductive) where
  growAxon = return NoAxon

-- TODO: Make an incubation version of growNerve which would follow if it was correctly attached (but how to follow if it is used as an option to a neuron and is consumed there?)
{-|
Grows an unattached 'Nerve'. By specifying type of the 'Nerve' you can specify conductivity of both directions (which is then
type checked for consistency around the program) and thus specify which 'Impulse's you are interested in (and thus limit possible
memory leak). With type of 'Impulse's this 'Nerve' is capable of conducting you can also specify which 'Neuron' you are interested
in 'grow'ing on the one end of the 'Nerve'.

For example, you could grow a 'Nerve' for "Control.Etage.Sequence" 'Neuron' and 'Neuron' itself like this:

> nerve <- growNerve :: IO (Nerve (SequenceFromImpulse Int) AxonConductive (SequenceForImpulse Int) AxonNonConductive)
> neuron <- attach defaultOptions nerve

and for example print all 'Impulse's as they are coming in:

> print =<< getContentsFromNeuron nerve

Check 'growNeuron' for a more high-level function (of 'Incubation') which both 'grow's a 'Neuron' and corresponding 'Nerve' taking
care of all the details. Use this function only if you need decoupled 'grow'ing.
-}
growNerve :: (Impulse from, Impulse for, GrowAxon (Axon from fromConductivity), GrowAxon (Axon for forConductivity)) => IO (Nerve from fromConductivity for forConductivity)
growNerve = do
  from <- growAxon
  for <- growAxon
  return $ Nerve from for

{-|
Crosses axons around in a 'Nerve'. Useful probably only when you want to 'attachTo' 'Nerve' so that it looks as 'Impulse's are comming
from a 'Neuron' and are not send to a 'Neuron'. So in this case you are 'attach'ing 'Nerve' in a direction away from a 'Neuron' and not
towards it, what is a default.

For example, you can do something like this:

> nerveDump <- (growNeuron :: NerveOnlyFor DumpNeuron) defaultOptions
> nerveOnes <- (growNeuron :: NerveOnlyFrom (SequenceNeuron Int)) (\o -> o { valueSource = repeat 1 })
> nerveTwos <- (growNeuron :: NerveOnlyFrom (SequenceNeuron Int)) (\o -> o { valueSource = repeat 2 })
> 
> nerveOnes `attachTo` [TranslatableFor (cross nerveTwos)]
> nerveTwos `attachTo` [TranslatableFor nerveDump]

Of course in this example you could simply 'attachTo' both 'Nerve's to "Control.Etage.Dump" 'Neuron'. So 'cross' is probably useful
only when using 'Nerve's unattached to its 'Neuron' (made by 'growNerve', for example) and/or when using such 'Nerve's with
'Neuron's which operate on how 'Impulse's are 'propagate'd (or 'fuse'd).
-}
cross :: Nerve from fromConductivity for forConductivity -> Nerve for forConductivity from fromConductivity
cross (Nerve from for) = Nerve for from