<?php defined('SYSPATH') or die('No direct script access.');
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

require_once(Kohana::find_file('libraries', 'bugzilla', TRUE, 'php'));
require_once(Kohana::find_file('libraries', 'Correlation', TRUE, 'php'));
require_once(Kohana::find_file('libraries', 'crash', TRUE, 'php'));
require_once(Kohana::find_file('libraries', 'release', TRUE, 'php'));
require_once(Kohana::find_file('libraries', 'timeutil', TRUE, 'php'));

/**
 * Reports based on top crashing signatures
 */
class Topcrasher_Controller extends Controller {
    public $host = "";
    public $tcbInitParams = null;

    /**
     * Constructor
     */
    public function __construct()
    {
        parent::__construct();
        $this->topcrashers_model = new Topcrashers_Model();
        $this->bug_model = new Bug_Model;
    }

    /**
     * Handle empty version values in the methods below, and redirect accordingly.
     *
     * @param   string  The product name
     * @param   string  The method name
     * @return  void
     */
    private function _handleEmptyVersion($product, $method) {
        $product_version = $this->branch_model->getRecentProductVersion($product);
        if (empty($product_version)) {
            // If no current major versions are found, grab any available version
            $product_versions = $this->branch_model->getCurrentProductVersionsByProduct($product);
            if (isset($product_versions[0])) {
                $product_version = array_shift($product_versions);
            }
        }

        $version = $product_version->version;
        $this->chooseVersion(
            array(
                'product' => $product,
                'version' => $version,
                'release' => null
            )
        );

        url::redirect('topcrasher/'.$method.'/'.$product.'/'.$version);
    }

    /**
     * Verify that the chosen version is valid given the current product.  If
     * not, throw a 404 error.
     *
     * @return void
     */
    private function _versionExists($version) {
        if (!$this->versionExists($version)) {
            Kohana::show_404();
        }
    }

    /**
     * Generates the index page.
     */
    public function index() {
        $products = $this->featured_versions;
        $product = null;

        if(empty($products)) {
            Kohana::show_404();
        }

        foreach($products as $individual) {
            if($individual->release == 'major') {
                $product = $individual;
            }
        }

        if(empty($product)) {
            $product = array_shift($products);
        }

        return url::redirect('/topcrasher/byversion/' . $product->product . '/' . $product->version);
    }

    /**
     * Raises a HTTP 500 error and populates view with response
     * @param string current selected product
     * @param string current selected version
     * @param object the response object
     * @return void
     */
    private function raise500Error($product=null, $version=null, $resp=null) {
        header("Data access error", TRUE, 500);
        $this->setViewData(
            array(
                'nav_selection' => 'top_crashes',
                'product'       => $product,
                'url_nav'       => url::site('products/'.$product),
                'version'       => $version,
                'resp'          => $resp
            )
        );
    }

    /**
     * Returns the correct UI string for the provided OS
     *
     * @param string    operating system name
     * @return string   correctly formatted OS string
     */
    private function getOSDisplayName($os)
    {
        $formattedOS = "";

        if(stripos($os, "win") !== false) {
            $formattedOS = Kohana::config('platforms.win_name');
        } else if(stripos($os, "mac") !== false) {
            $formattedOS = Kohana::config('platforms.mac_name');
        } else if(stripos($os, "lin") !== false) {
            $formattedOS = Kohana::config('platforms.lin_name');
        } else {
            $formattedOS = "Unsupported OS Name.";
        }
        return $formattedOS;
    }

    /**
     * @param   string  The name of the product
     * @param   version The version number for this product
     * @param   int     The number of days for which to display results
     * @param   string  The crash type to query by
     * @param   string  The operating system
     * @param   string Whether the report needs to be generated by build date
     * @return  object  Contains parameters needed by individual top crasher functions
     */
    public function initTopCrasher($product=null, $version=null, $duration=null, $crash_type=null, $os=null, $date_range_type='report')
    {
        if(is_null($product)) {
            Kohana::show_404();
        }

        $this->navigationChooseVersion($product, $version);
        if (empty($version)) {
            $this->_handleEmptyVersion($product, 'byversion');
        } else {
            $this->_versionExists($version);
        }

        $this->tcbInitParams->{'version'} = $version;

        if (empty($duration)) {
            $duration = Kohana::config('products.duration');
        }

        $this->tcbInitParams->{'duration'} = $duration;

        $this->tcbInitParams->{'duration_url_path'} =  $this->tcbInitParams->{'platform_url_path'} = array(Router::$controller, Router::$method, $product, $version);
        $this->tcbInitParams->{'durations'} = Kohana::config('topcrashbysig.durations');

        $this->tcbInitParams->{'cache_in_minutes'} = Kohana::config('webserviceclient.topcrash_vers_rank_cache_minutes', 60);
        $this->tcbInitParams->{'end_date'} = date('Y-m-d\TH:i:s+0000', TimeUtil::roundOffByMinutes($this->tcbInitParams->{'cache_in_minutes'}));
        // $dur is number of hours
        $this->tcbInitParams->{'dur'} = $duration * 24;
        $this->tcbInitParams->{'limit'} = Kohana::config('topcrashbysig.byversion_limit', 300);
        // lifetime in seconds
        $this->tcbInitParams->{'lifetime'} = $this->tcbInitParams->{'cache_in_minutes'} * 60;

        $this->tcbInitParams->{'crash_types'} = Kohana::config('topcrashbysig.crash_types');
        if (empty($crash_type) || !in_array($crash_type, $this->tcbInitParams->{'crash_types'})) {
            $crash_type = Kohana::config('topcrashbysig.crash_types_default');
        }
        $this->tcbInitParams->{'crash_type'} = $crash_type;

        $this->tcbInitParams->{'platforms'} = Kohana::config('platforms.platforms');

        $params['product'] = $product;
        $params['version'] = $version;
        
        if($os != null) {
            $this->tcbInitParams->{'os_display_name'} = $this->getOSDisplayName($os);
            $params['os'] = $os;
        }

        if($date_range_type == 'build') {
            $params['date_range_type'] = $date_range_type;
        }

        $params['crash_type'] = $this->tcbInitParams->crash_type;
        $params['end_date'] = $this->tcbInitParams->end_date;
        $params['duration'] = $this->tcbInitParams->dur;
        $params['limit'] = $this->tcbInitParams->limit;
        
        $this->tcbInitParams->{'service_uri'} = $this->topcrashers_model->buildURI($params, "crashes");

        return $this->tcbInitParams;
    }

    private function setSignature($signature, $top_crasher)
    {
        //$top_crasher->{'missing_sig_param'} - optional param, used for formating url to /report/list
        if (is_null($signature)) {
            $top_crasher->{'display_signature'} = Crash::$null_sig;
            $top_crasher->{'display_null_sig_help'} = TRUE;
            $top_crasher->{'missing_sig_param'} = Crash::$null_sig_code;
        } else if(empty($signature)) {
            $top_crasher->{'display_signature'} = Crash::$empty_sig;
            $top_crasher->{'display_null_sig_help'} = TRUE;
            $top_crasher->{'missing_sig_param'} = Crash::$empty_sig_code;
        } else {
            $top_crasher->{'display_full_signature'} = $signature;
            $top_crasher->{'display_signature'} = substr($signature, 0, 80);
            $top_crasher->{'display_null_sig_help'} = FALSE;
        }
    }

    /**
     * Display the top crashers by product, version and build date.
     *
     * @param   string  The name of the product
     * @param   version The version  number for this product
     * @param   int     The number of days for which to display results
     * @param   string  The crash type to query by
     * @return  void
     */
    public function by_build_date($product=null, $version=null, $duration=null, $crash_type=null) 
    {
        if(is_null($product)) {
          Kohana::show_404();
        }

        $params = $this->initTopCrasher($product, $version, $duration, $crash_type, null, "build");

        $byversion_url_path = array(Router::$controller, "byversion", $product, $version);
        $platform_url_path = array(Router::$controller, "byos", $product, $version);

        $resp = $this->topcrashers_model->getTopCrashers($params);

        if($resp) {

            $signatures = array();

            foreach($resp->crashes as $top_crasher) {

                if ($this->input->get('format') != "csv") {
                    $this->setSignature($top_crasher->signature, $top_crasher);

                    $top_crasher->{'display_percent'} = number_format($top_crasher->percentOfTotal * 100, 2) . "%";
                    $top_crasher->{'display_previous_percent'} = number_format($top_crasher->previousPercentOfTotal * 100, 2) . "%";
                    $top_crasher->{'display_change_percent'} = number_format($top_crasher->changeInPercentOfTotal * 100, 2) . "%";
                    if(isset($top_crasher->startup_percent) && !empty($top_crasher->startup_percent)) {
                        $top_crasher->{'startup_crash'} = (round($top_crasher->startup_percent * 100) > 50);
                    }
                    array_push($signatures, $top_crasher->signature);

                    $top_crasher->{'correlation_os'} = Correlation::correlationOsName($top_crasher->win_count, $top_crasher->mac_count, $top_crasher->linux_count);
                }
                $top_crasher->trendClass = $this->topcrashers_model->addTrendClass($top_crasher->changeInRank);
            }

            $signature_to_bugzilla = $this->bug_model->bugsForSignatures(
                array_unique($signatures),
                Kohana::config('codebases.bugTrackingUrl')
            );

            if ($this->input->get('format') == "csv") {
                $this->setViewData(array('top_crashers' => $this->_csvFormatArray($resp->crashes)));
                $this->renderCSV("${product}_${version}_" . date("Y-m-d"));
            } else {
                $this->setViewData(array(
                    'product'        => $product,
                    'version'        => $version,
                    'byversion_url'   => url::site(implode($byversion_url_path, '/')),
                    'crash_types'    => $params->crash_types,
                    'crash_type'     => $params->crash_type,
                    'crash_type_url' => url::site(implode($params->{'duration_url_path'}, '/') . '/' . $params->{'duration'} . '/'),
                    'date_range_type'=> 'build',
                    'duration_url'   => url::site(implode($params->{'duration_url_path'}, '/') . '/'),
                    'duration'       => $params->{'duration'},
                    'durations'      => $params->{'durations'},
                    'has_builds'     => $this->branch_model->hasBuilds($product . ":" . $version),
                    'start_date'     => $resp->start_date,
                    'end_date'       => $resp->end_date,
                    'percent_total'  => $resp->totalPercentage,
                    'platforms'      => Kohana::config('platforms.platforms'),
                    'platform_url'   => url::site(implode($platform_url_path, '/') . '/'),
                    'range_unit'     => 'days',
                    'range_value'    => $params->{'duration'},
                    'sig2bugs'       => $signature_to_bugzilla,
                    'top_crashers'   => $resp->crashes,
                    'total_crashes'  => $resp->totalNumberOfCrashes,
                ));
            }
        } else {
            $this->raise500Error($product, $version, $resp);
        }
    }

    /**
     * Display the top crashers by product & version.
     *
     * @param   string  The name of the product
     * @param   version The version  number for this product
     * @param   int     The number of days for which to display results
     * @param   string  The crash type to query by
     * @return  void
     */
    public function byversion($product=null, $version=null, $duration=null, $crash_type=null)
    {
        if(is_null($product)) {
          Kohana::show_404();
        }

        $params = $this->initTopCrasher($product, $version, $duration, $crash_type);

        $platform_url_path = array(Router::$controller, "byos", $product, $version);

        $resp = $this->topcrashers_model->getTopCrashers($params);

        if($resp) {
            $this->topcrashers_model->ensureProperties($resp, array(
                     'start_date' => '',
                     'end_date' => '',
                     'totalPercentage' => 0,
                     'crashes' => array(),
                     'totalNumberOfCrashes' => 0), 'top crash sig overall');
            $signatures = array();
            $req_props = array(
                'signature' => '',
                'count' => 0,
                'win_count' => 0,
                'mac_count' => 0,
                'linux_count' => 0,
                'currentRank' => 0,
                'previousRank' => 0,
                'changeInRank' => 0,
                'percentOfTotal' => 0,
                'previousPercentOfTotal' => 0,
                'changeInPercentOfTotal' => 0,
                'versions' => '',
                'versions_count' => '',
                'first_report' => '',
                'first_report_exact' => ''
            );

            foreach($resp->crashes as $top_crasher) {
                $this->topcrashers_model->ensureProperties($top_crasher, $req_props, 'top crash sig trend crashes');

                if ($this->input->get('format') != "csv") {
                    $this->setSignature($top_crasher->signature, $top_crasher);

                    $top_crasher->{'display_percent'} = number_format($top_crasher->percentOfTotal * 100, 2) . "%";
                    $top_crasher->{'display_previous_percent'} = number_format($top_crasher->previousPercentOfTotal * 100, 2) . "%";
                    $top_crasher->{'display_change_percent'} = number_format($top_crasher->changeInPercentOfTotal * 100, 2) . "%";
                    if(isset($top_crasher->startup_percent) && !empty($top_crasher->startup_percent)) {
                        $top_crasher->{'startup_crash'} = (round($top_crasher->startup_percent * 100) > 50);
                    }
                    
                    array_push($signatures, $top_crasher->signature);

                    $top_crasher->{'correlation_os'} = Correlation::correlationOsName($top_crasher->win_count, $top_crasher->mac_count, $top_crasher->linux_count);
                }

                $top_crasher->trendClass = $this->topcrashers_model->addTrendClass($top_crasher->changeInRank);
            }

            $signature_to_bugzilla = $this->bug_model->bugsForSignatures(
                array_unique($signatures),
                Kohana::config('codebases.bugTrackingUrl')
            );

            if ($this->input->get('format') == "csv") {
                $this->setViewData(array('top_crashers' => $this->_csvFormatArray($resp->crashes)));
                $this->renderCSV("${product}_${version}_" . date("Y-m-d"));
            } else {
                $this->setViewData(array(
                    'resp'           => $resp,
                    'duration_url'   => url::site(implode($params->{'duration_url_path'}, '/') . '/'),
                    'last_updated'   => $resp->end_date,
                    'date_range_type'=> 'report',
                    'duration'       => $params->{'duration'},
                    'durations'      => $params->{'durations'},
                    'has_builds'     => $this->branch_model->hasBuilds($product . ":" . $version),
                    'percentTotal'   => $resp->totalPercentage,
                    'product'        => $product,
                    'version'        => $version,
                    'platforms'      => Kohana::config('platforms.platforms'),
                    'nav_selection'  => 'top_crashes',
                    'sig2bugs'       => $signature_to_bugzilla,
                    'start'          => $resp->start_date,
                    'end_date'       => $resp->end_date,
                    'crash_types'    => $params->crash_types,
                    'crash_type'     => $params->crash_type,
                    'crash_type_url' => url::site(implode($params->{'duration_url_path'}, '/') . '/' . $params->{'duration'} . '/'),
                    'platform_url'   => url::site(implode($platform_url_path, '/') . '/'),
                    'range_unit'     => 'days',
                    'range_value'    => $params->{'duration'},
                    'top_crashers'   => $resp->crashes,
                    'total_crashes'  => $resp->totalNumberOfCrashes,
                    'url_nav'        => url::site('products/'.$product),
                ));
            }
        } else {
            $this->raise500Error($product, $version, $resp);
        }
    }

    /**
     * Display the top crashers by product and OS
     *
     * @param   string  The name of the product
     * @param   version The version  number for this product
     * @param   string  The operating system
     * @param   int     The number of days for which to display results
     * @param   string  The crash type to query by
     * @return  void
     */
    public function byos($product=null, $version=null, $os=null, $duration=null, $crash_type=null, $date_range_type=null)
    {
        if(is_null($os)) {
            Kohana::show_404();
        }

        // if we access byos from by build date, we need to add build as a parameter to the service URL
        if($date_range_type == 'build') {
            $params = $this->initTopCrasher($product, $version, $duration, $crash_type, $os, "build");
        } else {
            $params = $this->initTopCrasher($product, $version, $duration, $crash_type, $os);
        }

        $resp = $this->topcrashers_model->getTopCrashers($params);

        if($resp) {
            $signatures = array();
            foreach($resp->crashes as $top_crasher) {

                if ($this->input->get('format') != "csv") {
                    $this->setSignature($top_crasher->signature, $top_crasher);

                    $top_crasher->{'display_percent'} = number_format($top_crasher->percentOfTotal * 100, 2) . "%";
                    $top_crasher->{'display_previous_percent'} = number_format($top_crasher->previousPercentOfTotal * 100, 2) . "%";
                    $top_crasher->{'display_change_percent'} = number_format($top_crasher->changeInPercentOfTotal * 100, 2) . "%";
                    if(isset($top_crasher->startup_percent) && !empty($top_crasher->startup_percent)) {
                        $top_crasher->{'startup_crash'} = (round($top_crasher->startup_percent * 100) > 50);
                    }

                    array_push($signatures, $top_crasher->signature);
                    $top_crasher->{'correlation_os'} = Correlation::correlationOsName($top_crasher->win_count, $top_crasher->mac_count, $top_crasher->linux_count);

                    $top_crasher->trendClass = $this->topcrashers_model->addTrendClass($top_crasher->changeInRank);
                }
            }

            $signature_to_bugzilla = $this->bug_model->bugsForSignatures(
                array_unique($signatures),
                Kohana::config('codebases.bugTrackingUrl')
            );

            $byversion_url_path = array(Router::$controller, "byversion", $product, $version);
            $bybuilddate_url_path = array(Router::$controller, "by_build_date", $product, $version);

            $os_id = Kohana::config('platforms.'.substr(strtolower($os), 0, 3).'_id');
            // Sadly we are not consistent in our naming conventions, so we have to use ugly hacks...
            if ($os_id == 'windows') {
                $os_id = 'win';
            }

            if ($this->input->get('format') == "csv") {
                $this->setViewData(array('top_crashers' => $this->_csvFormatArray($resp->crashes)));
                $this->renderCSV("${product}_${version}_" . date("Y-m-d"));
            } else {
                $this->setViewData(array(
                    'resp'              => $resp,
                    'product'           => $product,
                    'version'           => $params->version,
                    'os'                => $params->{'os_display_name'},
                    'os_id'             => $os_id,
                    'platforms'         => $params->{'platforms'},
                    'top_crashers'      => $resp->crashes,
                    'crash_types'       => $params->crash_types,
                    'crash_type'        => $params->crash_type,
                    'date_range_type'   => ($date_range_type != 'build' ? 'report' : 'build'),
                    'duration'          => $params->{'duration'},
                    'durations'         => $params->{'durations'},
                    'byversion_url'     => url::site(implode($byversion_url_path, '/')),
                    'by_date_range_url' => ($date_range_type != 'build'
                                            ? url::site(implode($byversion_url_path, '/'))
                                            : url::site(implode($bybuilddate_url_path, '/'))),
                    'crash_type_url'    => url::site(implode($params->{'duration_url_path'}, '/') . '/' . $os . '/' . $params->{'duration'} . '/'),
                    'duration_url'      => url::site(implode($params->{'duration_url_path'}, '/') . '/' . $os . '/'),
                    'platform_url'      => url::site(implode($params->{'platform_url_path'}, '/') . '/'),
                    'has_builds'        => $this->branch_model->hasBuilds($product . ":" . $version),
                    'start_date'        => $resp->start_date,
                    'end_date'          => $resp->end_date,
                    'range_unit'        => 'days',
                    'range_value'       => $params->{'duration'},
                    'sig2bugs'          => $signature_to_bugzilla
                ));
            }
        } else {
            $this->raise500Error($product, $version, $resp);
        }
    }

    /**
     * AJAX request for grabbing crash history data to be plotted
     * @param string - the product
     * @param string - the version
     * @param string - the signature OR $null_sig TODO
     * @param string    The start date by which to begin the plot
     * @param string    The end date by which to end the plot
     * @return responds with JSON suitable for plotting
     */
    public function plot_signature($product, $version, $start_date, $end_date, $signature)
    {
        //Bug#532434 Kohana is escaping some characters with html entity encoding for security purposes
        $signature = html_entity_decode($signature);

        header('Content-Type: text/javascript');
        $this->auto_render = FALSE;

        $config = array();
        $credentials = Kohana::config('webserviceclient.basic_auth');
        if ($credentials) {
            $config['basic_auth'] = $credentials;
        }
        $service = new Web_Service($config);

        $host = Kohana::config('webserviceclient.socorro_hostname');


        $cache_in_minutes = Kohana::config('webserviceclient.topcrash_vers_rank_cache_minutes', 60);

        $start_date = date('c', strtotime($start_date));
        $end_date = date('c', strtotime($end_date));
        $duration = rawurlencode(TimeUtil::determineHourDifferential($start_date, $end_date)); // Number of hours


        $start_date = rawurlencode($start_date);
        $end_date = rawurlencode($end_date);

        $limit = Kohana::config('topcrashbysig.byversion_limit', 300);
        $lifetime = $cache_in_minutes * 60; // Lifetime in seconds

        $p = rawurlencode($product);
        $v = rawurlencode($version);

        //Bug#534063
        if ($signature == Crash::$null_sig) {
            $signature = Crash::$null_sig_api_value;
        } else if($signature == Crash::$empty_sig) {
            $signature = Crash::$empty_sig_api_value;
        }
        $rsig = rawurlencode($signature);
        // Every 3 hours
        $resp = $service->get("${host}/topcrash/sig/trend/history/p/${p}/v/${v}/sig/${rsig}/end/${end_date}/duration/${duration}/steps/60", 'json', $lifetime);


        if ($resp) {
            $data = array(
                  'startDate' => $resp->{'start_date'},
                  'endDate'   => $resp->{'end_date'},
                  'signature' => $resp->signature,
                  'counts'    => array(),
                  'percents'  => array()
            );
            for ($i =0; $i < count($resp->signatureHistory); $i++) {
                $item = $resp->signatureHistory[$i];
                array_push($data['counts'], array(strtotime($item->date) * 1000, $item->count));
                array_push($data['percents'], array(strtotime($item->date) * 1000, $item->percentOfTotal * 100));
            }
            echo json_encode($data);
        } else {
            echo json_encode(array('error' => 'There was an error loading the data'));
        }
    }

    /**
     * Helper method for formatting a topcrashers list of objects into data
     * suitable for CSV output
     * @param array of topCrashersBySignature object
     * @return array of strings
     * @see Topcrashers_Model
     */
    private function _csvFormatArray($topcrashers)
    {
        $csvData = array(array('Rank', 'Change In Rank', 'Percentage of All Crashes',
                   'Previous Percentage', 'Signature',
                   'Total', 'Win', 'Mac', 'Linux',
                   'Version Count', 'Versions'));
        $i = 0;
        foreach ($topcrashers as $crash) {
            $line = array();
            $sig = strtr($crash->signature, array(
                ',' => ' ',
                '\n' => ' ',
                '"' => '&quot;'
            ));
            array_push($line, $i);
            array_push($line, $crash->changeInRank);
            array_push($line, $crash->percentOfTotal);
            array_push($line, $crash->previousPercentOfTotal);
            array_push($line, $sig);
            array_push($line, $crash->count);
            array_push($line, $crash->win_count);
            array_push($line, $crash->mac_count);
            array_push($line, $crash->linux_count);
            array_push($line, $crash->versions_count);
            array_push($line, str_replace(",",";",$crash->versions));
            array_push($csvData, $line);
            $i++;
        }
      return $csvData;
    }
}
