<?php
/* 
 *  Metode Triple Exponential Smoothing Holt-Winter Multiplicative 
 *
 *  factors / parameters:
 *  alpha: data smoothing factor 0 < alpha < 1
 *  beta: trend smoothing factor 0 < beta < 1
 *  gamma: seasonal smoothing factor 0 < gamma < 1
 *  L: period length
 * */

class ExponentialSmoothing
{
    private $alpha;
    private $beta;
    private $gamma;
    private $L; // periode bulan sebelumnya
    private $series; // data aktual
    private $levels;
    private $trends;
    private $seasonals;

    // digunakan untuk instansiasi object
    function __construct($alpha, $beta, $gamma, $L, $series)
    {
        $this->alpha = $alpha;
        $this->beta = $beta;
        $this->gamma = $gamma;
        $this->L = $L;
        $this->series = $series;

        $this->build_model();
    }

    // digunakan untuk perhitungan pemulusan, peramalan
    private function build_model()
    {   
        // Memanggil fungsi untuk menentukan nilai awal
        $this->initialize_levels();
        $this->initialize_trends();
        $this->initialize_seasonals();

        // fungsi untuk menghitung pemulusan dan peramalan
        for ($i = $this->L; $i < count($this->series); $i++) {
            $x = $this->series[$i];
            $s0 = $this->seasonals[$i-$this->L];
            $l0 = $this->levels[$i-1];
            $t0 = $this->trends[$i-1];

            // perhitungan rumus Lt, Bt, St
            $l = $this->alpha * $x / $s0 + (1 - $this->alpha) * ($l0 + $t0);
            $t = $this->beta * ($l - $l0) + (1 - $this->beta) * $t0; 
            $s = $this->gamma * ($x / $l) + (1 - $this->gamma) * $s0;

            $this->levels[$i] = $l;
            $this->trends[$i] = $t;
            $this->seasonals[$i] = $s;
        }
    }

    // digunakan untuk mengambil nilai dari perhitungan pemulusan tren
    public function get_trens(){
        return $this->trends;
    }
    
    // digunakan untuk mengambil nilai dari perhitungan pemulusan level
    public function get_levels(){
        return $this->levels;
    }

    // digunakan untuk mengambil nilai dari perhitungan pemulusan seasonal
    public function get_seasonals(){
        return $this->seasonals;
    }

    // fungsi digunakan untuk menginisialisasi nilai awal level
    private function initialize_levels()
    {
        $this->levels = array();
        $sum = 0;
        for ($i = 0; $i < $this->L - 1; $i++) {
            $this->levels[] = null;
            $sum += $this->series[$i];
        }
        $sum += $this->series[$this->L-1];
        $this->levels[] = $sum / $this->L;
        // echo "<br>level". $sum / $this->L;
    }

    // fungsi digunakan untuk menginisialisasi nilai awal tren
    private function initialize_trends()
    {
        $this->trends = array();
        for ($i = 0; $i < $this->L - 1; $i++) {
            $this->trends[] = null;
        }

        $y = 0;
        $sum = 0;
        for ($i = 0; $i < $this->L; $i++) {
            $y = $this->L+$i;
            $sum += ($this->series[$y]-$this->series[$i])/$this->L;
            // echo "<br>series y".($y+1).": ". $this->series[$y];
            // echo "<br>series i".($i+1).": ". $this->series[$i];
        }

        
        $nilaiAwal = $sum/$this->L;
        $this->trends[] = $nilaiAwal; 
    }

    // fungsi digunakan untuk menginisialisasi nilai awal seasonal
    private function initialize_seasonals()
    {
        $this->seasonals = array();
        for ($i = 0; $i < $this->L; $i++) {
            $this->seasonals[] = $this->series[$i] / $this->levels[$this->L-1];
            // echo "<br>sesional".$i .": ". $this->series[$i] / $this->levels[$this->L-1];
        }
    }

    // Fungsi untuk mengolah peramalan periode bulan selanjutnya
    public function forecast($k)
    {
        $m = $k - count($this->series) + 1;
        if ($m <= 0) {
            throw new Exception("Supposed to forecast future series");
        }

        // Rumus : (Lt+bt*m)*St-s+m
        $i = count($this->series)-1;
        $j = $i - $this->L + (($m-1) % $this->L) + 1;
        // echo "<br>j : ". $j;
        // echo "<br>m : ". $m . "<br>";        
        $forecast = ($this->levels[$i] + $m * $this->trends[$i]) * $this->seasonals[$j];

        return $forecast;
    }

    // Fungsi ini digunakan untuk perhitungan peralaman pada data aktual bulan sebelumnya
    public function get_forecast()
    {
        /*
        Keterangan :
        Lt = Pemulusan Level
        Bt = Pemulusan Trend
        St = Pemulusan Musiman
        Ft+m = Peramalan untuk periode ke depan.

        Rumus : (Lt+bt*m)*St-s+m
        */

        $data = array();
        $m = 1;
        for ($i = 0; $i < count($this->series); $i++) {
            $j = ($i)-$this->L;

            if ($i>=$this->L) {
                // echo "<br>lt".($i).": ". $this->levels[$i-1];
                // echo "<br>btm".($i).": ". $this->trends[$i-1];
                // echo "<br>sts".($i).": ". $this->seasonals[$j];
                $forecast = ($this->levels[$i-1] + $m * $this->trends[$i-1]) * $this->seasonals[$j];
                // echo "<br>ft".($i+1).": ". $forecast;
                // echo " <br>";
                $data[] = $forecast;
            }else{
                $data[] = null;
            }
        }

        return $data;
    }
}
