# Advent of Code

This module provides a command line interface for running the solution to a
specific day for a specific year, run as `advent $YEAR $DAY`.

This runner will automatically locate the appropriate input file and then run
the selected day's solution against it. See [Runner](Runner.md) for the
definitions of the `Day`, `Year`, and `Advent` data types.

```idris
module Main

import System
import System.Path
import System.File
import System.Directory
import System.Console.GetOpt
import Data.String
import Data.Vect

import Control.Eff
import Derive.Prelude

import Structures.Dependent.FreshList

import Runner
import Util
import Util.Eff

import Years.Y2015;

%language ElabReflection
%default total
```

# Command Line Interface

Since this is a simple application, I am using `GetOpt` from `contrib` for
argument parsing. Here we define our options list, a data type to describe our
flags, and the usage message header.

```idris
data Flag = UseExample | Verbose
%runElab derive "Flag" [Eq, Show]

options : List (OptDescr Flag)
options =
  [
    MkOpt ['u'] ["use-example"] (NoArg UseExample) "Use example input instead of main input"
  , MkOpt ['v'] ["verbose"] (NoArg Verbose) "Enable logging to stderr"
  ]

header : String
header = "Usage: advent YEAR DAY [OPTION...]"
```

## Error Type

We will be using one unified error type for the entire `Main` module, but we
must go ahead and define it before we go any further with command line arguments
parsing, as we are about to do some things that might fail.

```idris
data Error : Type where
  OptionsError : (errs : List String) -> Error
  ArgumentsError : (count : Nat) -> Error
  InvalidYear : (year : String) -> Error
  InvalidDay : (year : Nat) -> (day : String) -> Error
  NoSuchDay : (year : Nat) -> (day : Nat) -> Error
  NoCurrentDir : Error
  InputRead : (path : String) -> (err : FileError) -> Error
  SolveError : Show e => (year, day, part : Nat) -> (err : e) -> Error
%name Error err
```

A `Show` implementation for `Error` is provided, hidden in this document for
brevity.

```idris hide
Show Error where
  show (OptionsError errs) =
    let errs = unlines . map ("  " ++) . lines . joinBy "\n" $ errs
    in "Error parsing options:\n" ++ errs ++ "\n" ++ usageInfo header options
  show (ArgumentsError count) =
    "Error parsing arguments: Expected 2 positional arguments, got \{show count}. Provide only year and date.\n" ++
    usageInfo header options
  show (InvalidYear year) =
    "Error parsing arguments: Unable to parse year argument: \{year}"
  show (InvalidDay year day) =
    "Error parsing arguments: Failed to parse day \{day} of year \{show year}"
  show (NoSuchDay year day) =
    "Error locating day: No such day \{show day} of year \{show year}"
  show NoCurrentDir =
    "Unknown error getting current working directory"
  show (InputRead path err) =
    let err = unlines . map ("  " ++) . lines . show $ err
    in "Error reading input: Encountered error reading input at \{path}\n" ++ err
  show (SolveError year day part err) =
    let err = unlines . map ("  " ++) . lines . show $ err
    in "Error solving day \{show day} part \{show part} of year \{show year}: \n" ++ err
```

## Extract the year and day

After peeling off our options from the arguments using `GetOpt`, we will be left
with a list of non-option arguments, which we must then parse into our year
number/day number pair. We perform some pattern matching to make sure the list
is the correct length, and then parse the individual integers, throwing an error
if anything goes wrong

```idris
||| Convert the non-option arguments into a Year/Day pair
argumentsToPair : Has (Except Error) fs =>
  List String -> Eff fs (Nat, Nat)
argumentsToPair [] = throw $ ArgumentsError 0
argumentsToPair [x] = throw $ ArgumentsError 1
argumentsToPair [year, day] = do
  year <- note (InvalidYear year) $ parsePositive year
  day <- note (InvalidDay year day) $ parsePositive day
  pure (year, day)
argumentsToPair xs = throw $ ArgumentsError (length xs)
```

# The Advent

Construct our top-level `Advent` record from the imported `Year` modules. The
argument to the `MkAdvent` constructor is a `FreshList`, with a freshness
criteria ensuring that the list is in ascending order and does not include any
duplicate `Year`s.

```idris
advent : Advent
advent = MkAdvent [
    y2015
  ]
```

# The Hard Part

Now we must glue everything together, parse our arguments, handle any supplied
options, locate the relevant year and day, locate the corresponding input file,
read it, and then run the selected `Day` against it.

Because we are building our application around effects, the actual core of the
application is the effectful `start` computation, that the `main` function will
then apply `IO` based handlers to.

Our reader and writer aren't ready yet, we need to do some setup first, so the
top level type signature doesn't include them, so we'll add them to the effects
list as they become available.

```idris
covering
||| Actual main, as an effectful computation
start : Eff [IO, Except Error] ()
start = do
```

## Parsing the arguments

Feed the augments from `System.getArgs` into `getOpt`, making sure to drop the
name the binary was invoked with, then check for any errors in the result,
throwing our own error if any are encountered.

```idris
  -- Read and parse arguments/options
  args <- getArgs
  let opts = getOpt Permute options (drop 1 args)
  when (not . null $ opts.errors) $
    throw (OptionsError opts.errors)
```

Use our `argumentsToPair` function to extract the selected year and day numbers
from the positional arguments, implicitly throwing an error if there are any
failures doing so.

```idris
  (year, day_n) <- argumentsToPair opts.nonOptions
```

## Handling the arguments and finding the input

Handle the verbosity flag, if it is set, hook our logger up to stderr, otherwise
blackhole the logs. Afterwards, use `logHandler` to introduce the `Logger` into
the effects list.

```idris
  -- If the verbose flag is set, hook up the logging writer to stderr
  let verbosity = count (== Verbose) opts.options
  let logHandler : Eff [IO, Except Error, Logger] () -> Eff [IO, Except Error] () =
    handleLog (Warn >+ verbosity)
  -- Add the logging writer to the effect
  logHandler $ do
```

Find the current directory and append to it to find the input file. If the
`--use-example/-u` flag is set, look for the input file at
`inputs/examples/$year/$day`, otherwise look for it at `inputs/real/$year/$day`.

After we've located it, attempt to read from it, throwing an `InputRead` error
if any errors occur, then insert the contents into our `Reader` and inject it
into the effects list.

```idris
  -- Locate and read in the input file
  Just cwd <- currentDir | _ => throw NoCurrentDir
  let cwd = parse cwd
  let use_example = any (== UseExample) opts.options
  let base_dir = cwd /> "inputs" /> (if use_example then "examples" else "real");
  let input_path = show $ base_dir /> (show year) /> (show day_n)
  info "Reading input from \{input_path}"
  Right contents <- readFile input_path | Left err => throw $ InputRead input_path err
  -- Now add the reader that provides the input to the effect
  runReaderAt "input" contents {fs = PartEff Error} $ do
```

Try and locate the `Day` in our `Advent` record, throwing an error if we are
unable to locate it

```idris
  -- Attempt to locate the provided day
  Just day <- pure $ locate year day_n advent | _ => throw $ NoSuchDay year day_n
```

## Executing the parts

Run the selected `Day`'s part 1, wrapping any errors that it returns in a
`SolveError`. Collect both the result and the opaque context value, then print
out the result.

```idris
  -- Run part 1
  part_1 <- lift $ runExcept day.part1
  (part_1, ctx) <- rethrow . mapLeft (SolveError year day_n 1 @{day.showErr1}) $ part_1
  putStrLn "\{show year} day \{show day_n} part 1:"
  putStrLn $ unlines . map ("  " ++) . lines . show @{day.showOut1} $ part_1
```

Check and see if the selected `Day` has a part 2, if it does, feed the opaque
context value collected from part 1 into it, wrap any errors that it returns in
a `SolveError`, then print out the result, then return, closing out the program.

```idris
  -- Run part 2 if we have it
  case day.part2 of
    Nothing => pure ()
    Just part_2_f => do
      part_2 <- lift . runExcept $ part_2_f ctx
      part_2 <- rethrow . mapLeft (SolveError year day_n 2 @{day.showErr2}) $ part_2
      putStrLn "\{show year} day \{show day_n} part 2:"
      putStrLn $ unlines . map ("  " ++) . lines . show @{day.showOut2} $ part_2
```

## Helper functions

```idris
  where
```

### Lower logging into the IO component of the effect

Makes use of `Logger`'s `handleLoggerIO` function to "lower" logging actions
into `IO` within the effect.

```idris
    -- Lowers logging into IO within the effect using the given IO function
    handleLog :
      Has Logger fs => Has IO (fs - Logger) => 
      (max_log : Level) -> Eff fs a -> Eff (fs - Logger) a
    handleLog max_log x = 
      handle 
        (\msg, f => 
          do _ <- send $ handleLoggerIO max_log msg 
             f $ ignore msg) x
```

# Handle the effectful action as an IO action

Use `runEff` to translate the effects in `start`'s effects list into IO actions,
and return the resulting `IO ()`.

The `IO` component of the effects list is handled via `id`, translating `IO`
actions into themselves, and we construct a helper function to handle errors as
IO actions by printing the error and crashing the program.

```idris
covering
||| Shim main, which simply executes the effectful computation
main : IO ()
main = runEff start [id, handleError]
  where
  handleError : Except Error a -> IO a
  handleError (Err err) = do
    printLn err
    exitFailure
```