{- |
  Legion is a mathematically sound framework for writing horizontally
  scalable user applications. Historically, horizontal scalability has
  been achieved via the property of statelessness. Programmers would
  design their applications to be free of any kind of persistent state,
  avoiding the problem of distributed state management. This almost never
  turns out to really be possible, so programmers achieve "statelessness"
  by delegating application state management to some kind of external,
  shared database -- which ends up having its own scalability problems.

  In addition to scalability problems, which modern databases (especially
  NoSQL databases) have done a good job of solving, there is another,
  more fundamental problem facing these architectures: The application
  is not really stateless.

  Legion is a Haskell framework that abstracts state partitioning, data
  replication, request routing, and cluster rebalancing, making it easy
  to implement large and robust distributed data applications.

  Examples of services that rely on partitioning include ElasticSearch,
  Riak, DynamoDB, and others. In other words, almost all scalable
  databases.
-}

module Network.Legion (
  -- * Using Legion

  -- ** Starting the Legion Runtime
  -- $startup
  forkLegionary,
  StartupMode(..),
  Runtime,

  -- ** Runtime Configuration
  -- $framework-config
  RuntimeSettings(..),

  -- ** Making Runtime Requests
  makeRequest,
  search,

  -- * Implementing a Legion Application
  -- $service-implementaiton

  -- ** Indexing
  -- $indexing
  Indexable(..),
  LegionConstraints,
  Persistence(..),
  Event(..),
  Tag(..),

  -- * Other Types
  SearchTag(..),
  IndexRecord(..),
  PartitionKey(..),
  PartitionPowerState,

  -- * Utils
  newMemoryPersistence,
  diskPersistence,
) where

import Prelude hiding (lookup, readFile, writeFile, null)

import Network.Legion.Application (LegionConstraints,
  Persistence(Persistence, getState, saveState, list))
import Network.Legion.Basics (newMemoryPersistence, diskPersistence)
import Network.Legion.Index (Tag(Tag, unTag), IndexRecord(IndexRecord,
  irTag, irKey), SearchTag(SearchTag, stTag, stKey),
  Indexable(indexEntries))
import Network.Legion.PartitionKey (PartitionKey(K, unKey))
import Network.Legion.PartitionState (PartitionPowerState)
import Network.Legion.PowerState (Event(apply))
import Network.Legion.Runtime (StartupMode(NewCluster, JoinCluster),
  forkLegionary, Runtime, makeRequest, search)
import Network.Legion.Settings (RuntimeSettings(RuntimeSettings,
  adminHost, adminPort, peerBindAddr, joinBindAddr))

--------------------------------------------------------------------------------

-- $service-implementaiton
-- Whenever you use Legion to develop a distributed application, your
-- application is going to be divided into two major parts, the state/less/
-- part, and the state/ful/ part. The stateless part is going to be the
-- context in which a legion node is running -- probably a web server if you
-- are exposing your application as a web service. Legion itself is focused
-- mainly on the stateful part, and it will do all the heavy lifting on
-- that side of things. However, it is worth mentioning a few things about
-- the stateless part before we move on.
--
-- The unit of state that Legion knows about is called a \"partition\". Each
-- partition is identified by a 'PartitionKey', and it is replicated across
-- the cluster. Each partition acts as the unit of state for handling
-- stateful user requests which are routed to it based on the `PartitionKey`
-- associated with the request. What the stateful part of Legion is
-- /not/ able to do is figure out what partition key is associated with
-- the request in the first place. This is a function of the stateless
-- part of the application. Generally speaking, the stateless part of
-- your application is going to be responsible for
--
--   * Starting up the Legion runtime using 'forkLegionary'.
--   * Identifying the partition key to which a request should be applied
--     (e.g.  maybe this is some component of a URL, or else an identifier
--     stashed in a browser cookie).
--   * Marshalling application requests into requests to the Legion runtime.
--   * Marshalling the Legion runtime response into an application response.
--
-- Legion doesn't really address any of these things, mainly because there
-- are already plenty of great ways to write stateless services. What
-- Legion does provide is a runtime that can be embedded in the stateless
-- part of your application, that transparently handles all of the hard
-- stateful stuff, like replication, rebalancing, request routing, etc.
--
-- The only thing required to implement a legion service is to implement
-- a few typeclasses and call 'forkLegionary'. The state-aware part of
-- your application will live mostly within the request handler, which
-- is implemented via a typeclass `Event`.
-- 
-- > class Event e o s | e -> s o where
-- >   apply :: e -> s -> (o, s)
-- 
-- If you look at 'apply', you will see that it is abstract over the type
-- variables @e@, @o@, and @s@.  These are the types your application
-- has to fill in. @e@ stands for "event", which is the type of requests
-- your application accepts; @o@ stands for "output", which is the type of
-- responses your application will generate in response to those requests,
-- and @s@ stands for "state", which is the application state that each
-- partition can assume.
-- 
-- Implementing a request handler is pretty straight forward, but
-- there is a little bit more to it than meets the eye. If you look at
-- 'forkLegionary', you will see a constraint named @'LegionConstraints'
-- e o s@, which is short-hand for a long list of typeclasses that your
-- @e@, @o@, and @s@ types are going to have to implement.
--
-- The persistence layer provides the framework with a way to store the
-- various partition states. This allows you to choose any number of
-- persistence strategies, including only in memory, on disk, or in some
-- external database.
--
-- See 'newMemoryPersistence' and 'diskPersistence' if you need to get
-- started quickly with an in-memory persistence layer.

--------------------------------------------------------------------------------

-- $indexing
-- Legion gives you a way to index your partitions so that you can find
-- partitions that have certain characteristics without having to know
-- the partition key a priori. Conceptually, the "index" is a single,
-- global, ordered list of 'IndexRecord's. The 'search' function allows
-- you to scroll forward through this list at will.
--
-- Indexing is implemented by instantiating the 'Indexable' typeclass
-- for your state type.
--
-- > class Indexable s where
-- >   indexEntries :: s -> Set Tag
--
-- The tags returned by 'indexEntries' is used to construct a set of zero
-- or more 'IndexRecord's. For each 'Tag' returned by 'indexEntries',
-- an 'IndexRecord' is generated such that:
-- 
-- > IndexRecord {irTag = <your tag>, irKey = <partition key>}
   

--------------------------------------------------------------------------------

-- $startup
-- While this section is being worked on, you can check out the
-- [legion-discovery](https://github.com/owensmurray/legion-discovery)
-- project for an example of a stateful web services that advantage of
-- Legion's ability to define your own operations on your data. Take a look at
-- [`Network.Legion.Discovery.App`](https://github.com/owensmurray/legion-discovery/blob/master/src/Network/Legion/Discovery/App.hs)
-- to see where the magic of defining a Legion application happens. The rest
-- of the code is mostly just standard HTTP-interface-written-in-Haskell,
-- and requests sent to the Legion runtime.

--------------------------------------------------------------------------------

-- $framework-config
-- The legion framework has several operational parameters which can
-- be controlled using configuration. These include the address binding
-- used to expose the cluster management service endpoint and what file
-- to use for cluster state journaling.