/* @file KalmanFilterSpec.hpp */

#pragma once

#include "KalmanFilterStep.hpp"

namespace xo {
    namespace kalman {
        /* full specification for a kalman filter.
         *
         * For a textbook linear filter,  expect a KalmanFilterStep
         * instance to be independent of KalmanFilterState/KalmanFilterInput.
         *
         * Relaxing this requirement for two reasons:
         * 1. (proper) want to allow filter with variable timing between observations,
         *    expecially if observations are event-driven.
         *    In that case it's likely that state transition matrices are a function
         *    of elapsed time between observations.  Providing filter state sk
         *    allows MkStepFn to use sk.tm()
         * 2. (sketchy) when observations represent market data,
         *    desirable to treat an observation as giving one-sided information
         *    about true value.   For example treat a bid price as evidence
         *    true value is higher than that bid,  but don't want to constrain
         *    "how much higher".   Certainly no reason to think that
         *    bid price is normally distributed around fair value.
         *    Allow for hack in which we
         *    and modulate error distribution "as if it were normal" to assess
         *    a non-gaussian error distribution
         */
        class KalmanFilterSpec {
        public:
            /* typical implementation will look something like:
             *    mk_step(KalmanFilterState const & sk,
             *            KalmanFilterInputPtr const & zkp1)
             *    {
             *      KalmanFilterTransition model = ...;
             *      KalmanFilterObservable obs = ...;
             *
             *      return KalmanFilterStep(sk, model, obs, zkp1);
             *    }
             */
            using MkStepFn = std::function<KalmanFilterStep
                                           (rp<KalmanFilterState> const & sk,
                                            KalmanFilterInputPtr const & zkp1)>;

        public:
            explicit KalmanFilterSpec(rp<KalmanFilterStateExt> s0, MkStepFn mkstepfn)
                : start_ext_{std::move(s0)},
                  mk_step_fn_{std::move(mkstepfn)} {}

            rp<KalmanFilterStateExt> const & start_ext() const { return start_ext_; }
            /* get step parameters (i.e. matrices F, Q, H, R)
             * for step t(k) -> t(k+1).
             *
             * We supply t(k) state s and t(k+1) observation z(k+1):
             * - to allow time stepping to be observation-driven
             * - to allow for selective outlier removal
             */
            KalmanFilterStep make_step(rp<KalmanFilterState> const & sk,
                                       rp<KalmanFilterInput> const & zkp1) {
                return this->mk_step_fn_(sk, zkp1);
            } /*make_step*/

            void display(std::ostream & os) const;
            std::string display_string() const;

        private:
            /* starting state */
            rp<KalmanFilterStateExt> start_ext_;

            /* creates kalman filter step object on demand;
             * step object specifies inputs to 1 step in discrete
             * linear kalman filter
             */
            MkStepFn mk_step_fn_;
        }; /*KalmanFilterSpec*/

        inline std::ostream &
        operator<<(std::ostream & os, KalmanFilterSpec const & x) {
            x.display(os);
            return os;
        } /*operator<<*/
    } /*namespace kalman*/
} /*namespace xo*/

/* end KalmanFilterSpec.hpp */
