classdef MPCcasadi_v1_0 < matlab.System

    % Public, tunable properties
    properties(Nontunable)
        TimeStep = 0; % Time step MPC
        N        = 0; % Planning and control horizon N
        R        = 1; % Weights for control cost R 
        T        = 1; % Weights for slack variable for output constraints T 
        nState   = 0; % Number of states X
        nOut     = 0; % Number of outputs Y
        nIn      = 0; % Number of controlled inputs U
        nDst     = 0; % Number of disturbance inputs
        A        = 0; % A 
        Bd       = 0; % Bd (disturbance) 
        Bu       = 0; % Bu (control) 
        C        = 0; % C 
        D        = 0; % D 
        uMin     = 0; % Lower control constraints uMin
        uMax     = 0; % Upper control constraints uMax
        yMin     = 0; % Lower output constraints yMin
        yMax     = 0; % Upper output constraints yMax
    end

    properties(DiscreteState)

    end

    % Pre-computed constants
    properties(Access = private)
      casadi_solver
      lbg  
      ubg   
    end

    methods(Access = protected)
        function sts = getSampleTimeImpl(obj)
            sts = createSampleTime(obj, 'Type', 'Controllable', 'TickTime', obj.TimeStep); % Time step
        end
        
        function num = getNumInputsImpl(~) % Number of inputs
            num = 4;
        end
        function num = getNumOutputsImpl(~) % Number of outputs
            num = 5;
        end
        function [dt1, dt2, dt3, dt4, dt5] = getOutputDataTypeImpl(~) % Output data type
        	dt1 = 'double';
            dt2 = 'double';
            dt3 = 'double';
            dt4 = 'double';
            dt5 = 'double';
        end
        function dt1 = getInputDataTypeImpl(~) % Input data type
        	dt1 = 'double';
        end
        function [sz1, sz2, sz3, sz4, sz5] = getOutputSizeImpl(obj) % OUtput dimensions
        	sz1 = [1,        obj.nIn];     % mv
            sz2 = [obj.N+1,  obj.nState];  % xStar               
            sz3 = [obj.N,    obj.nOut];    % sStar
            sz4 = [obj.N,    obj.nIn];     % uStar
            sz5 = [1,        obj.nOut];    % yStarOut
        end
        function [sz1, sz2, sz3, sz4] = getInputSizeImpl(obj) % Input dimensions
        	sz1  = [obj.nState,  1];                 % xHat
            sz2  = [obj.N,       obj.nDst];          % disturbances
            sz3  = [obj.N,       1];                 % elec price
            sz4  = [1,           1];                 % on
        end
        function cp1 = isInputComplexImpl(~) % Inputs are complex numbers?
        	cp1 = false;
        end
        function [cp1, cp2, cp3, cp4, cp5] = isOutputComplexImpl(~) % Outputs are complex numbers?
        	cp1 = false;
            cp2 = false;
            cp3 = false;
            cp4 = false;            
            cp5 = false;
        end
        function fz1 = isInputFixedSizeImpl(~) % Input fixed size?
        	fz1 = true;
        end
        function [fz1, fz2, fz3, fz4, fz5] = isOutputFixedSizeImpl(~) % Output fixed size?
        	fz1 = true;
            fz2 = true;
            fz3 = true;
            fz4 = true;
            fz5 = true;
        end
        
        function setupImpl(obj)
            % Perform one-time calculations, such as computing constants
            import casadi.*

            %% Parameters
            nState  = obj.nState;
            nIn     = obj.nIn;
            nOut    = obj.nOut;
            nDst    = obj.nDst;
            N       = obj.N;
            R       = obj.R;
            T       = obj.T;
            A       = obj.A;
            Bd      = obj.Bd;
            Bu      = obj.Bu;
            C       = obj.C;
            D       = obj.D;

            %% Prepare variables
            U = MX.sym('U', nIn,     N); 
            P = MX.sym('P', nState + N + nDst*N); % Initial values, costElec, disturbances
            X = MX.sym('X', nState, (N+1));
            S = MX.sym('S', nOut,    N); % First state free

            J   = 0; % Objective function
            g   = [];  % constraints vector
            
            %% P indices
            iX0     = [1:nState];
            iCoEl   = [nState+1:nState+N];
            iDist   = [nState+N+1:nState+N+nDst*N];
            
            %% Disassemble P
            pX0     = P(iX0);
            pCoEl   = P(iCoEl);
            pDist   = reshape(P(iDist), [nDst N]); % Prone to shaping error

            %% Define variables
            states       = MX.sym('states',       nState);
            controls     = MX.sym('controls',     nIn);
            disturbances = MX.sym('disturbances', nDst);

            %% Dynamics
            f = Function('f',{P, states, controls, disturbances},{A*states + Bu*controls + Bd*disturbances});

            %% Compile all constraints
            g = [g; X(:,1) - pX0]; 

            for i = 1:N    
                g = [g; C*X(:,i+1) - S(:,i)];                           % State/output constraints, first state free
                g = [g; U(:,i)];                                        % Control constraints
                g = [g; X(:,i+1) - f(P, X(:,i), U(:,i), pDist(:,i))];   % System dynamics
                    
                % Cost function, first state given -> not punished
                J = J + R * U(:,i) * pCoEl(i) + S(:,i)'*T*S(:,i);
            end

            %% Reshape variables
            OPT_variables = veccat(X, S, U);

            %% Optimization
            nlp_mhe = struct('f', J,             ...
                             'x', OPT_variables, ...
                             'g', g,             ...
                             'p', P); 

            opts                   = struct;
            opts.ipopt.print_level = 0; %5;
            solver                 = nlpsol('solver', 'ipopt', nlp_mhe, opts);
            
            %% Pack opj
            obj.casadi_solver = solver;
            
        end

        function [mv, xStar, sStar, uStar, yStarOut] = stepImpl(obj, xHat, dist, cE, on) 
            % Implement algorithm. Calculate y as a function of input u and
            % discrete states.
            if on > 0.5
                %% Parameters
                nState  = obj.nState;
                N       = obj.N;
                nOut    = obj.nOut;
                nDst    = obj.nDst;
                nIn     = obj.nIn;
                yMin    = obj.yMin;
                yMax    = obj.yMax;
                uMin    = obj.uMin;
                uMax    = obj.uMax;
                C       = obj.C;
                solver  = obj.casadi_solver;
                Pdata   = [xHat; cE; reshape(dist', [nDst*N, 1])];   % Prone to shaping error!!!

                %% Constraints
                lbg = zeros(nState,1); % x0 constraints
                ubg = zeros(nState,1);
                
                % Output, control and dynamics constraints
                for i = 1:N
                    lbg = [lbg; yMin];
                    lbg = [lbg; uMin];
                    lbg = [lbg; zeros(nState,1)];

                    ubg = [ubg; yMax];
                    ubg = [ubg; uMax];
                    ubg = [ubg; zeros(nState,1)];
                end

                %% Solver
                sol    = solver('x0',  0,     ... % x0 = x* from before, shift one time step, double last time step
                                'lbg', lbg,   ...
                                'ubg', ubg,   ...
                                'p',   Pdata);

                %% Outputs
                xStar    = reshape(full(sol.x(1                    :nState*(N+1))),        [nState, (N+1)])';
                sStar    = reshape(full(sol.x(nState*(N+1)+1       :nState*(N+1)+nOut*N)), [nOut,    N])';
                uStar    = reshape(full(sol.x(nState*(N+1)+nOut*N+1:end)),                 [nIn,     N])';
                
                mv       =         full(sol.x(nState*(N+1)+nOut*N+1:nState*(N+1)+nOut*N+nIn))';
                yStarOut = C*xStar(2,:)'; % Second value is the target

            else % Zero output if MPC is disabled
                mv       = zeros(1,       obj.nIn);
                xStar    = zeros(obj.N+1, obj.nState);
                uStar    = zeros(obj.N,   obj.nIn);
                sStar    = zeros(obj.N,   obj.nOut);
                yStarOut = zeros(1,       obj.nOut);   
            end % \if on
        end % \stepImpl

        function resetImpl(obj)
            % Initialize / reset discrete-state properties
        end
    end
end
