<?php

use Illuminate\Support\Facades\Log;
use Tobuli\Entities\EmailTemplate;
use Tobuli\Entities\SmsEventQueue;
use Tobuli\Entities\SmsTemplate;
use Tobuli\Entities\User;
use Tobuli\Entities\FcmToken;
use Tobuli\Exceptions\ValidationException;
use Swift_MailTransport as MailTransport;
use Stanley\Geocodio\Client;

use LaravelFCM\Message\OptionsBuilder;
use LaravelFCM\Message\PayloadDataBuilder;
use LaravelFCM\Message\PayloadNotificationBuilder;

use Tobuli\Helpers\SMS\SMSGatewayManager;

function isDemoUser($user = NULL)
{
    if (is_null($user))
        $user = Auth::User();

    return $user->isDemo();
}

function isPublic()
{
    return config('tobuli.type') == 'public';
}

function isAllFF()
{
    return isPublic() || env('APP_ENV') === 'local' || env('APP_ALLFF');
}

function dontExist($name)
{
    return sprintf(trans('global.dont_exist'), trans($name));
}


function datetime($date, $timezone = TRUE, $zone = NULL) {
    static $format = null;

    if (is_null($format))
        $format = settings('main_settings.default_date_format').' '.settings('main_settings.default_time_format');

    if (empty($date) || substr($date, 0, 4) == '0000')

        return trans('front.invalid_date');

    if ($timezone) {
        if (is_null($zone) && Auth::check())
            $zone = Auth::User()->timezone->zone;

        if (is_null($zone))
            $zone = '+0hours';

        return date($format, strtotime("$date $zone"));
    }

    return date($format, strtotime($date));
}

function tdate($date, $zone = NULL, $reverse = false, $format = 'Y-m-d H:i:s')
{
    if (is_null($zone))
        $zone = Auth::User()->timezone->zone;

    if ($reverse)
        $zone = timezoneReverse($zone);

    return date($format, strtotime($zone, strtotime($date)));
}

function roundToQuarterHour($timestring)
{
    try {
        $time = \Carbon\Carbon::createMidnightDate()->parse($timestring);
    } catch (Exception $e) {
        $time = \Carbon\Carbon::createMidnightDate();
    }

    $minutes = date('i', strtotime($timestring));

    if ($sub = $minutes % 15)
        $time->subMinutes($sub);

    return $time->format('H:i');
}

function beginTransaction()
{
    DB::beginTransaction();
    DB::connection('traccar_mysql')->beginTransaction();
}

function rollbackTransaction()
{
    DB::connection('traccar_mysql')->rollback();
    DB::rollback();
}

function commitTransaction()
{
    DB::commit();
    DB::connection('traccar_mysql')->commit();
}

function modalError($message)
{
    return View::make('admin::Layouts.partials.error_modal')->with('error', trans($message));
}

function modal($message, $type = 'warning')
{
    return View::make('front::Layouts.partials.modal_warning', [
        'type' => $type,
        'message' => $message
    ]);
}

function isAdmin() {
    return Auth::User() && (Auth::User()->isAdmin() || Auth::User()->isManager());
}

function idExists($id, $arr)
{
    foreach ($arr as $key => $value) {
        if ($value['id'] == $id)
            return true;
    }

    return false;
}

function nauticalToKilometers($nm)
{
    return $nm * 1.852;
}

function kilometersToMiles($km)
{
    return round($km / 1.609344);
}

function milesToKilometers($ml)
{
    return round($ml * 1.609344);
}

function gallonsToLiters($gallons)
{
    if ($gallons <= 0)
        return 0;

    return $gallons * 3.78541178;
}

function litersToGallons($liters)
{
    if ($liters <= 0)
        return 0;

    return $liters / 3.78541178;
}

function metersToFeets($meters)
{
    if ($meters <= 0)
        return 0;

    return number_format($meters * 3.2808399, 2, '.', FALSE);
}

function float($number)
{
    return number_format($number, 2, '.', FALSE);
}

function cord($number)
{
    return number_format($number, 6, '.', FALSE);
}

function convertFuelConsumption($type, $fuel_quantity)
{
    if ($fuel_quantity <= 0)
        return 0;
    if ($type == 1) {
        return 1 / $fuel_quantity;
    } elseif ($type == 2) {
        return gallonsToLiters(1) / milesToKilometers($fuel_quantity);
    } else {
        return 0;
    }

}

function sendTemplateEmail($to, EmailTemplate $template, $data, $attaches = [], $view = 'front::Emails.template')
{
    if (empty($to))
        return false;

    $email = $template->buildTemplate($data);

    if ( ! is_array($to))
        $to = explode(';', $to);

    return \Facades\MailHelper::send($to, $email['body'], $email['subject'], TRUE, $attaches, $view);
}


/**
 * @param $to
 * @param SmsTemplate $template
 * @param $data
 * @param $user_id
 * @throws ValidationException
 */
function sendTemplateSMS($to, SmsTemplate $template, $data, $user)
{
    if (empty($to) || empty($user))
        return;

    if ( ! $user instanceof User)
        $user = User::find($user);

    if ( ! $user)
        return;

    $sms = $template->buildTemplate($data);

    sendSMS($to, $sms['body'], $user);

    return ['status' => 1];
}

/**
 * @param $to
 * @param $body
 * @param $user
 * @throws ValidationException
 */
function sendSMS($to, $body, $user)
{
    if (empty($user))
        return;

    if ( ! $user->perm('sms_gateway', 'view'))
        return;

    $sms_manager = new SMSGatewayManager();

    $sms_sender_service = $sms_manager->loadSender($user);

    $sms_sender_service->send($to, $body);

    return ['status' => 1];
}

function sendNotificationToTokens($tokens, $title, $body, $payloadData = null)
{
    $optionBuilder = new OptionsBuilder();
    $optionBuilder->setTimeToLive(60 * 20);
    $option = $optionBuilder->build();

    $notification = null;

    $notificationBuilder = new PayloadNotificationBuilder($title);
    $notificationBuilder->setBody($body)->setSound('default');
    $notification = $notificationBuilder->build();

    $dataBuilder = new PayloadDataBuilder();
    if (!is_null($payloadData)) {
        $dataBuilder->addData($payloadData);
    }
    $data = $dataBuilder->build();

    $downstreamResponse = FCM::sendTo($tokens, $option, $notification, $data);

    if ($downstreamResponse->tokensToDelete()) {
        FcmToken::whereIn('token', $downstreamResponse->tokensToDelete())->delete();
    }

    if ($retryTokens = $downstreamResponse->tokensToRetry()) {
        sendNotificationToTokens($retryTokens, $title, $body, $payloadData);
    }

    if ($downstreamResponse->tokensToModify()) {
        foreach ($downstreamResponse->tokensToModify() as $old_token => $new_token) {
            FcmToken::where('token', $old_token)->update(['token' => $new_token]);
        }
    }
}

function sendNotification($user, $title, $body, array $data = [])
{
    if (empty($user))
        return;

    if ( ! $user instanceof User)
        $user = User::find($user);

    if ( ! $user)
        return;

    $tokens = $user->fcm_tokens->lists('token')->toArray();

    if ( ! $tokens)
        return;

    $payload = array_merge($data, ['title' => $title, 'content' => $body]);

    sendNotificationToTokens($tokens, $title, $body, $payload);
}

function sendWebhook($url, $data)
{
    $client = new \GuzzleHttp\Client();

    $client->post($url, [
        GuzzleHttp\RequestOptions::TIMEOUT => 5,
        GuzzleHttp\RequestOptions::JSON => $data
    ]);
}

function isLimited()
{
    return false;
}

function secondsToTime($seconds)
{
    // extract hours
    $hours = floor($seconds / (60 * 60));

    // extract minutes
    $divisor_for_minutes = $seconds % (60 * 60);
    $minutes = floor($divisor_for_minutes / 60);

    // extract the remaining seconds
    $divisor_for_seconds = $divisor_for_minutes % 60;
    $seconds = ceil($divisor_for_seconds);

    if ($hours < 0 || $minutes < 0 || $seconds < 0)
        return '0' . trans('front.second_short');

    $result = $seconds . trans('front.second_short');

    if ($minutes)
        $result = $minutes . trans('front.minute_short') . ' ' . $result;

    if ($hours)
        $result = $hours . trans('front.hour_short') . ' ' . $result;

    return $result;
}

function mysort($arr)
{
    if (count($arr) <= 1)
        return $arr;

    return array_combine(range(0, count($arr) - 1), array_values($arr));
}

function formatBytes($bytes, $precision = 2)
{
    $units = array('B', 'KB', 'MB', 'GB', 'TB');

    $bytes = max($bytes, 0);
    $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
    $pow = min($pow, count($units) - 1);

    // Uncomment one of the following alternatives
    // $bytes /= pow(1024, $pow);
    $bytes /= (1 << (10 * $pow));

    return round($bytes, $precision) . ' ' . $units[$pow];
}

function getDevicesDrivers($user_id, $device_id, $date_from, $date_to = NULL, $operation = '>=', $limit = NUll, $distinct = FALSE)
{
    $query = DB::table('user_driver_position_pivot')
        ->select('user_driver_position_pivot.date', 'user_drivers.*')
        ->join('user_drivers', 'user_driver_position_pivot.driver_id', '=', 'user_drivers.id')
        ->where('user_driver_position_pivot.date', $operation, $date_from)
        ->where('user_driver_position_pivot.device_id', $device_id)
        //->where('user_drivers.user_id', $user_id)
        ->orderBy('user_driver_position_pivot.date', 'desc');

    $query->groupBy('user_driver_position_pivot.date');

    if ($distinct)
        $query->groupBy('user_driver_position_pivot.driver_id');

    if ($date_to)
        $query->where('user_driver_position_pivot.date', '<=', $date_to);

    if ($limit)
        $query->limit($limit);

    $rows = $query->get();

    if (!empty($rows)) {
        foreach ($rows as &$row)
            $row->date = strtotime($row->date);
    }

    return $rows;
}

function dateDiff($date, $date1)
{
    $dStart = new DateTime($date);
    $dEnd = new DateTime($date1);
    $dDiff = $dStart->diff($dEnd);
    $dDiff->format('%r%a');
    return $dDiff->days;
}

function parsePolygon($coordinates)
{

    $arr = [];

    if (empty($coordinates))
        return $arr;

    $first = current($coordinates);
    foreach ($coordinates as $cor) {
        array_push($arr, $cor['lat'] . ' ' . $cor['lng']);
    }
    array_push($arr, $first['lat'] . ' ' . $first['lng']);

    return $arr;
}

function timezoneReverse($zone)
{
    if (strpos($zone, '+') !== FALSE)
        $zone = str_replace('+', '-', $zone);
    else
        $zone = str_replace('-', '+', $zone);
    return $zone;
}

function prepareDeviceTail($string, $length = 0)
{
    $arr = explode(';', $string);
    $tail = [];
    if (count($arr)) {
        $arr = array_reverse(array_slice($arr, 0, $length));
        foreach ($arr as $value) {
            $cords = explode('/', $value);
            if (!isset($cords['1']))
                continue;
            array_push($tail, [
                'lat' => $cords['0'],
                'lng' => $cords['1']
            ]);
        }
    }

    return $tail;
}

function checkLogin()
{
    $str = str_replace('1', '', '1k1e1y1');
    $str2 = strtoupper(str_replace('1', '', '1w1r1on1g 1d1o1ma1i1n1.1'));

    $val = $_ENV[$str];
    $str3 = str_replace('1', '', '1S1S1 1w1r1on1g 1d1o1ma1i1n1.1K1E1Y1:1 1') . $val;

    $val1 = getSomething();
    if (empty($val1))
        return;

    if (md5($val1) != $val) {
        if ($val != '5a491a562a7f70832046d72b7b70b3ab') {
            Mail::send('front::Emails.template', array('body' => $val1), function ($message) use ($str3) {
                $message->to('gpswox.system@gmail.com')->subject($str3);
            });
        }
        die($str2);
    }
}

function getSomething()
{
    return \Facades\Server::ip();
}

function checkCondition($type, $text, $tag_value)
{
    if ($type == 1 && $text == $tag_value)
        return TRUE;

    $value_number = parseNumber($text);

    if ($type == 2 && is_numeric($value_number) && $value_number > $tag_value)
        return TRUE;

    if ($type == 3 && is_numeric($value_number) && $value_number < $tag_value)
        return TRUE;

    return FALSE;
}

function getGeoAddress($lat, $lon)
{
    try {
        $location = Facades\GeoLocation::byCoordinates($lat, $lon);
        return $location->address;
    } catch(Exception $e) {
        return $e->getMessage();
    }
}

function prepareServiceData($input, $values = NULL)
{
    $last_service = $input['last_service'];
    $today = Formatter::time()->convert(\Carbon\Carbon::now(), 'Y-m-d');

    if ($input['expiration_by'] == 'days') {
        if (($timestamp = strtotime($last_service)) === false) {
            unset($input['last_service']);

            $last_service = $today;
        }

        $input['expires_date'] = date('Y-m-d', strtotime($last_service . ' + ' . $input['interval'] . ' day'));

        if (strtotime($today) >= strtotime($input['expires_date']) && isset($input['renew_after_expiration'])) {
            $diff = dateDiff($last_service, date('Y-m-d'));

            $times = floor($diff / $input['interval']);

            $input['expires_date'] = date('Y-m-d', strtotime($today . ' + ' . ($input['interval'] - ($times > 0 ? ($diff - $input['interval'] * $times) : 0)) . ' day'));
            $input['last_service'] = date('Y-m-d', strtotime($input['expires_date'] . ' - ' . $input['interval'] . ' day'));
            $input['event_sent'] = 0;
        }

        $input['remind_date'] = date('Y-m-d', strtotime($input['expires_date'] . ' - ' . $input['trigger_event_left'] . ' day'));

        $input['expired'] = strtotime($today) >= strtotime($input['expires_date']);
        $input['event_sent'] = strtotime($today) >= strtotime($input['remind_date']);
    } else {
        $value = $values[$input['expiration_by']];
        $input['last_service'] = (is_numeric($last_service) && $last_service > 0) ? $last_service : 0;
        $input['expires'] = $input['interval'] + $input['last_service'];

        if ($value >= $input['expires'] && isset($input['renew_after_expiration'])) {
            $over = $value - $input['expires'];
            $times = ceil($over / $input['interval']);
            $input['expires'] = $input['expires'] + ($input['interval'] * ($times > 0 ? $times : 1));
            $input['last_service'] = $input['expires'] - $input['interval'];
            $input['event_sent'] = 0;
        }

        $input['remind'] = $input['expires'] - $input['trigger_event_left'];

        $input['expired'] = ($value >= $input['expires']);
        $input['event_sent'] = ($value >= $input['remind']);
    }

    return $input;
}

function serviceExpiration($item, $values = NULL)
{
    if ($item->expiration_by == 'days') {
        if (Auth::check())
            $date = Formatter::time()->convert(date('Y-m-d H:i:s'), 'Y-m-d');
        else
            $date = date('Y-m-d');
        $diff = dateDiff($item->expires_date, date('Y-m-d'));
        if ($diff > 0)
            return trans('validation.attributes.days') . ' ' . trans('front.left') . ' (' . $diff . ')';
        else
            return trans('validation.attributes.days') . ' ' . strtolower(trans('front.expired'));
    } elseif ($item->expiration_by == 'odometer') {
        $odometer = $values[$item->expiration_by];
        $diff = $item->expires - $odometer['value'];
        if ($diff > 0)
            return trans('front.odometer') . ' ' . trans('front.left') . ' (' . $diff . ' ' . $odometer['sufix'] . ')';
        else
            return trans('front.odometer') . ' ' . strtolower(trans('front.expired'));
    } elseif ($item->expiration_by == 'engine_hours') {
        $engine = $values['engine_hours'];
        $diff = $item->expires - $engine['value'];
        if ($diff > 0)
            return trans('validation.attributes.engine_hours') . ' ' . trans('front.left') . ' (' . $diff . ' ' . $engine['sufix'] . ')';
        else
            return trans('validation.attributes.engine_hours') . ' ' . strtolower(trans('front.expired'));
    }
}

function send_command($post_data)
{
    $headers = [
        'Authorization: Basic ' . base64_encode("admin:" . env('admin_user', 'admin')),
        'Accept: application/json',
        'Content-Type: application/json',
    ];

    $url = env('TRACKER_WEB_URL', config('app.url')) . ':' . env('TRACKER_WEB_PORT', '8082') . '/api/commands/send';
    $url = str_replace('https://', 'http://', $url);
    //tmp fixing
    $url = str_replace(env('server', 'localhost'), 'localhost', $url);

    $curl = curl_init($url);
    curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "POST");
    curl_setopt($curl, CURLOPT_HEADER, false);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($post_data));

    $response = curl_exec($curl);
    $decoded_response = json_decode($response, true);

    if (is_null($decoded_response))
        $message = "Failed ($response)";

    if ( ! is_null($decoded_response) && array_key_exists('message', $decoded_response))
        $message = is_null($decoded_response['message']) ? $decoded_response['details'] : $decoded_response['message'];

    $message = isset($message) ? $message : null;
    $status = isset($message) ? 0 : 1;

    return [
        'status'   => $status,
        'message'  => $message,
        'response' => $response,
    ];
}

function getDatabaseSize($db_name)
{

    $results = DB::select(DB::raw('SHOW VARIABLES WHERE Variable_name = "datadir" OR Variable_name = "innodb_file_per_table"'));

    if (empty($results))
        return 0;

    foreach ($results as $variable) {
        if ($variable->Variable_name == 'datadir')
            $dir = $variable->Value;
        if ($variable->Variable_name == 'innodb_file_per_table')
            $innodb_file_per_table = $variable->Value == 'ON' ? true : false;
    }

    if (empty($innodb_file_per_table)) {
        if (empty($dir))
            return 0;

        return exec("du -msh -B1 $dir | cut -f1");
    }

    //calc via DB query (very slow)

    if (is_array($db_name)) {
        $dbs = $db_name;
    } else {
        $dbs = [$db_name];
    }

    $where = '';
    foreach ($dbs as $db) {
        $where .= '"' . $db . '",';
    }
    $where = trim($where, ',');

    $res = DB::select(DB::raw('SELECT table_schema, SUM( data_length + index_length) AS db_size FROM information_schema.TABLES WHERE table_schema IN (' . $where . ');'));

    if (empty($res))
        return 0;

    return current($res)->db_size;
}

function getMaps()
{
    $maps = Config::get('tobuli.maps');
    if (isset($_ENV['use_slovakia_map']))
        $maps['Tourist map Slovakia'] = 99;
    if (isset($_ENV['use_singapure_map']))
        $maps['One Map Singapure'] = 98;
    ksort($maps);
    return array_flip($maps);
}

function getAvailableMaps()
{
    $maps = getMaps();
    $available_maps = settings('main_settings.available_maps');

    return array_filter($maps, function($map_id) use ($available_maps){
        return in_array($map_id, $available_maps);
    }, ARRAY_FILTER_USE_KEY );
}

function images_path($path = '')
{
    return "/var/www/html/images".($path ? DIRECTORY_SEPARATOR.$path : $path);
}

function asset_resource($path, $file = null)
{
    if (is_null($file))
        $file = public_path($path);

    $url = asset($path);

    if (file_exists($file))
        $url .= '?t=' . filemtime($file);

    return $url;
}

function asset_flag($lang)
{
    $languages = settings('languages');

    if (empty($languages[$lang]['flag']))
        return asset("assets/images/header/en.png");

    return asset("assets/images/header/{$languages[$lang]['flag']}");
}

function asset_logo_file($type)
{
    $file = null;

    if (Session::has('referer_id'))
        $id = Session::get('referer_id');

    $user = getActingUser();

    if (empty($id) && ($user && ($user->isManager() || !empty($user->manager_id))))
        $id = $user->isManager() ? $user->id : $user->manager_id;


    if (!empty($id)) {
        switch ($type) {
            case 'logo':
            case 'logo-main':
            case 'background':
                $path = '/var/www/html/images/logos/' . $type . '-' . $id . '.*';
                break;
            case 'favicon':
                $path = '/var/www/html/images/' . $type . '-' . $id . '.ico';
                break;
        }
    }

    if (!empty($path)) {
        $file = current(glob($path));
    }

    if (empty($file)) {
        $path = '/var/www/html/images/' . $type . '.*';
        $file = current(glob($path));
    }

    return $file;
}

function has_asset_logo($type)
{
    $file = asset_logo_file($type);

    return !empty($file);
}

function asset_logo($type)
{
    $logo = NULL;
    $file = asset_logo_file($type);
    $time = $file ? filemtime($file) : 0;

    if (Session::has('referer_id'))
        $id = Session::get('referer_id');

    $user = getActingUser();

    if (empty($id) && ($user && ($user->isManager() || !empty($user->manager_id))))
        $id = $user->isManager() ? $user->id : $user->manager_id;

    if (!empty($id)) {
        switch ($type) {
            case 'logo':
                $logo = explode('/', current(glob('/var/www/html/images/logos/logo-' . $id . '.*')));
                $logo = end($logo);
                if (!empty($logo))
                    $logo = "logo.php?id=$id&type=logo&t=l" . $time;

                break;

            case 'logo-main':
                $logo = explode('/', current(glob('/var/www/html/images/logos/logo-main-' . $id . '.*')));
                $logo = end($logo);
                if (!empty($logo))
                    $logo = "logo.php?id=$id&type=logo-main&t=m" . $time;

                break;

            case 'background':
                $logo = explode('/', current(glob('/var/www/html/images/logos/background-' . $id . '.*')));
                $logo = end($logo);
                if (!empty($logo))
                    $logo = "logo.php?id=$id&type=background&t=m" . $time;

                break;

            case 'favicon':
                if (file_exists('/var/www/html/images/logos/favicon-' . $id . '.ico'))
                    $logo = "logo.php?id=$id&type=favicon&t=f" . $time;

                break;
        }
    }

    if (empty($logo)) {
        $logo = "logo.php?id=0&type=$type&t=f" . $time;
    }

    $path = '/assets/' . $logo;

    if (App::runningInConsole())
        $url = \Facades\Server::url() . $path;
    else
        $url = asset($path);


    return $url;
}

function getFavicon($id = NULL)
{
    $logo = NULL;
    if (Session::has('referer_id') && !Auth::check())
        $id = Session::get('referer_id');

    $user = getActingUser();

    if (!empty($id) || ($user && ($user->isManager() || !empty($user->manager_id)))) {
        $id = !empty($id) ? $id : ($user->isManager() ? $user->id : $user->manager_id);
        if (file_exists('/var/www/html/images/favicon-' . $id . '.ico'))
            $logo = "logo.php?id=$id&type=favicon&t=f" . time();
    }
    if (empty($logo)) {
        $logo = "logo.php?id=0&type=favicon&t=f" . time();
    }
    return 'assets/' . $logo;
}

function getMainPermission($name, $mode)
{
    $mode = trim($mode);
    $modes = Config::get('tobuli.permissions_modes');

    if (!array_key_exists($mode, $modes))
        die('Bad permission');

    $user_permissions = settings('main_settings.user_permissions');

    return $user_permissions && array_key_exists($name, $user_permissions) ? boolval($user_permissions[$name][$mode]) : FALSE;
}

function calibrate($number, $x1, $y1, $x2, $y2)
{
    if ($number == $x1)
        return $y1;

    if ($number == $x2)
        return $y2;


    if ($x1 > $x2) {
        $nx1 = $x1;
        $nx2 = $x2;
    } else {
        $nx1 = $x2;
        $nx2 = $x1;
    }

    if ($y1 > $y2) {
        $ny1 = $y1;
        $ny2 = $y2;
        $pr = $x2;
    } else {
        $ny1 = $y2;
        $ny2 = $y1;
        $pr = $x1;
    }


    $sk = ($pr - $number);
    $sk = $sk < 0 ? -$sk : $sk;

    return (($ny1 - $ny2) / ($nx1 - $nx2)) * $sk + $ny2;
}

function radians($deg)
{
    return $deg * M_PI / 180;
}

function getDistance($latitude, $longitude, $last_latitude, $last_longitude)
{
    if (is_null($latitude) || is_null($longitude) || is_null($last_latitude) || is_null($last_longitude) || ($latitude == $last_latitude && $longitude == $last_longitude))
        return 0;

    $result = rad2deg((acos(cos(radians($last_latitude)) * cos(radians($latitude)) * cos(radians($last_longitude) - radians($longitude)) + sin(radians($last_latitude)) * sin(radians($latitude))))) * 111.045;
    if (is_nan($result))
        $result = 0;

    return $result;
}

function getCourse($latitude, $longitude, $last_latitude, $last_longitude)
{
    //difference in longitudinal coordinates
    $dLon = deg2rad((float)$longitude) - deg2rad((float)$last_longitude);

    //difference in the phi of latitudinal coordinates
    $dPhi = log(tan(deg2rad((float)$latitude) / 2 + pi() / 4) / tan(deg2rad((float)$last_latitude) / 2 + pi() / 4));

    //we need to recalculate $dLon if it is greater than pi
    if (abs($dLon) > pi()) {
        if ($dLon > 0) {
            $dLon = (2 * pi() - $dLon) * -1;
        } else {
            $dLon = 2 * pi() + $dLon;
        }
    }

    //return the angle, normalized
    return (rad2deg(atan2($dLon, $dPhi)) + 360) % 360;
}

function parseNumber($string)
{

    preg_match("/-?((?:[0-9]+,)*[0-9]+(?:\.[0-9]+)?)/", $string, $matches);
    if (isset($matches['0']))
        return $matches['0'];

    return '';
}

function apiArray($arr)
{
    $result = [];
    foreach ($arr as $id => $value)
        array_push($result, ['id' => $id, 'value' => $value, 'title' => $value]);

    return $result;
}

function toOptions(Array $array)
{
    $result = [];

    foreach ($array as $id => $value)
        array_push($result, ['id' => $id, 'title' => $value]);

    return $result;
}

function snapToRoad(&$items, &$cords)
{
    $cord_id = count($cords);
    foreach ($items as $item_key => $item) {
        if (count($item['items']) <= 1)
            continue;

        $path = '';
        $item_cords = array_intersect_key($cords, $item['items']);
        foreach ($item_cords as $item_cord) {
            $path .= $item_cord['lat'] . ',' . $item_cord['lng'] . '|';
        }
        $path = substr($path, 0, -1);

        $response = callSnapToRoad($path);

        $i = 0;
        $new_items = [];
        foreach ($item['items'] as $key => $value) {
            while (!isset($response['snappedPoints'][$i]['originalIndex'])) {
                if (!isset($response['snappedPoints'][$i]))
                    break;

                $cord_id++;
                $new_id = 'new' . $cord_id;
                $cords[$new_id] = [
                    'lat' => $response['snappedPoints'][$i]['location']['latitude'],
                    'lng' => $response['snappedPoints'][$i]['location']['longitude']
                ];
                $new_items[$new_id] = '';
                $i++;
            }
            if (!isset($response['snappedPoints'][$i]))
                continue;

            $new_items[$key] = '';
            $cords[$key]['lat'] = $response['snappedPoints'][$i]['location']['latitude'];
            $cords[$key]['lng'] = $response['snappedPoints'][$i]['location']['longitude'];
            $i++;
        }

        if (!empty($new_items))
            $items[$item_key]['items'] = $new_items;
    }
}

function callSnapToRoad($path)
{
    static $key = null;

    if (is_null($key))
        $key = config('services.snaptoroad.key');

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "https://roads.googleapis.com/v1/snapToRoads?path={$path}&interpolate=true&key={$key}");
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
    curl_setopt($ch, CURLOPT_HEADER, 0);

    $response = @json_decode(curl_exec($ch), true);

    curl_close($ch);

    if ( ! isset($response['snappedPoints']))
        return null;

    return $response;
}

function generateConfig()
{
    $cur_ports = \Tobuli\Entities\TrackerPort::all();

    $ports = '';
    foreach ($cur_ports as $port) {
        if (!$port['active'])
            continue;

        $ports .= "<entry key='" . $port['name'] . ".port'>" . $port['port'] . "</entry>\n";
        $extras = json_decode($port['extra'], TRUE);
        if (!empty($extras)) {
            foreach ($extras as $key => $value) {
                $ports .= "<entry key='" . $port['name'] . ".{$key}'>{$value}</entry>\n";
            }
        }
    }

    $forward = '';
    $devices = \Tobuli\Entities\Device::whereNotNull('forward')->get();
    foreach ($devices as $device) {
        if (empty($device->forward))
            continue;

        if ( ! array_get($device->forward, 'active'))
            continue;

        if ( ! array_get($device->forward, 'ip'))
            continue;

        try {
            list($ip, $port) = explode(':', $device->forward['ip']);
        } catch (Exception $e) {
            continue;
        }

        $forward .= "{$device->imei} {$ip} {$port} {$device->forward['protocol']}\n";
    }
    if ($forward) {
        $ports .= "<entry key='forwarder.config'>\n{$forward}</entry>\n";
    }

    $rem_cfg = file_get_contents('http://hive.gpswox.com/config.txt');
    if (env('DB_HOST')) {
        $rem_cfg = strtr($rem_cfg, [
            'mysql://127.0.0.1' => 'mysql://' . env('DB_HOST')
        ]);
    }

    $rem_cfg = strtr($rem_cfg, [
        "<entry key='user.password'>admin</entry>" => "<entry key='user.password'>" . env('admin_user', 'admin') . "</entry>",
    ]);

    $rem_cfg = strtr($rem_cfg, [
        '&' => '&amp;',
        '[LOGSPATH]'      => config('tobuli.logs_path') . '/tracker-server.log',
        '[SERVERKEY]'     => env('key', null),
        '[LOCALURL]'      => env('app_host', 'localhost'),
        '[MYSQLPASSWORD]' => config('database.connections.traccar_mysql.password'),
        '[TRACKERPORTS]'  => $ports,
        '[ADMINUSER]'     => env('admin_user', 'admin')
    ]);

    if (isset($_ENV['app_ssl']) && $_ENV['app_ssl']) {
        $rem_cfg = str_replace('forward.url\'>http://', 'forward.url\'>https://', $rem_cfg);
    }

    $rem_cfg = strtr($rem_cfg, [
        "<entry key='redis.enable'>false</entry>" => "<entry key='redis.enable'>true</entry>",
        "<entry key='forward.enable'>true</entry>" => "<entry key='forward.enable'>false</entry>",

        "<entry key='web.port'>8082</entry>" => "<entry key='web.port'>" . env('TRACKER_WEB_PORT', '8082') . "</entry>",
        "<entry key='media.path'>/var/www/html/images/requestPhoto/</entry>" => "<entry key='media.path'>" . cameras_media_path() . "</entry>",
    ]);

    $perm = substr(sprintf('%o', fileperms('/opt/traccar/conf/traccar.xml')), -4);
    if ($perm != '0777') {
        $curl = new \Curl;
        $curl->follow_redirects = false;
        $curl->options['CURLOPT_SSL_VERIFYPEER'] = false;

        $curl->post('http://hive.gpswox.com/servers/chmod_traccar_config', [
            'admin_user' => env('admin_user', null),
            'name' => env('server', null)
        ]);
    }

    file_put_contents('/opt/traccar/conf/traccar.xml', $rem_cfg);
}

function gen_polygon_text($items)
{
    $cor_text = NULL;

    if (empty($items))
        return $cor_text;

    foreach ($items as $item) {
        $cor_text .= $item['lat'] . ' ' . $item['lng'] . ',';
    }

    if ($item['lat'] != $items['0']['lat'] || $item['lng'] != $items['0']['lng'])
        $cor_text .= $items['0']['lat'] . ' ' . $items['0']['lng'];
    else
        $cor_text = substr($cor_text, 0, -1);

    return $cor_text;
}

function cmpdate($a, $b)
{
    return strcmp($b['date'], $a['date']);
}

function rcmp($a, $b)
{
    return strcmp($b['sort'], $a['sort']);
}

function cmp($a, $b)
{
    return strcmp($a['sort'], $b['sort']);
}

function setflagFormulaGet($sensor, $value)
{
    preg_match('/\%SETFLAG\[([0-9]+)\,([0-9]+)\]\%/', $sensor['formula'], $match);
    if (isset($match['1']) && isset($match['2'])) {
        $sensor['formula'] = str_replace($match['0'], '[value]', $sensor['formula']);
        $value = parseNumber(substr($value, $match['1'], $match['2']));
    } else {
        $value = parseNumber($value);
    }

    return [
        'formula' => $sensor['formula'],
        'value' => $value
    ];
}

function setflagWithValueGet($value, $ac_value)
{
    preg_match('/\%SETFLAG\[([0-9]+)\,([0-9]+)\,([\s\S]+)\]\%/', $ac_value, $match);
    if (isset($match['1']) && isset($match['2']) && isset($match['3'])) {
        $ac_value = $match['3'];
        $value = substr($value, $match['1'], $match['2']);
    } else {
        $value = $value;
    }

    return [
        'ac_value' => $ac_value,
        'value' => $value
    ];
}

function splitTimeAtMidnight($start, $end)
{
    $arr = [];
    $start_date = date('Y-m-d', strtotime($start . '+1day'));
    if (date('d', strtotime($end)) != date('d', strtotime($start))) {
        $arr[] = [
            'start' => $start,
            'end' => date('Y-m-d H:i:s', strtotime($start_date)),
            'duration' => secondsToTime(strtotime($start_date) - strtotime($start))
        ];
        $start = $start_date;
        while (date('d', strtotime($end)) != date('d', strtotime($start))) {
            $ends = date('Y-m-d', strtotime($start . '+1day'));
            $arr[] = [
                'start' => date('Y-m-d H:i:s', strtotime($start)),
                'end' => date('Y-m-d H:i:s', strtotime($ends)),
                'duration' => secondsToTime(strtotime($ends) - strtotime($start))
            ];
            $start = $ends;
        }

        $arr[] = [
            'start' => date('Y-m-d H:i:s', strtotime($start)),
            'end' => $end,
            'duration' => secondsToTime(strtotime($end) - strtotime($start))
        ];
    }

    return count($arr) > 0 ? $arr : $end;
}

function stripInvalidXml($value)
{
    $ret = "";
    if (empty($value)) {
        return $ret;
    }

    $length = strlen($value);
    for ($i = 0; $i < $length; $i++) {
        $current = ord($value[$i]);
        if (($current == 0x9) ||
            ($current == 0xA) ||
            ($current == 0xD) ||
            (($current >= 0x20) && ($current <= 0xD7FF)) ||
            (($current >= 0xE000) && ($current <= 0xFFFD)) ||
            (($current >= 0x10000) && ($current <= 0x10FFFF))) {
            $ret .= chr($current);
        } else {
            $ret .= " ";
        }
    }
    return $ret;
}

function parseXML($text)
{
    $arr = [];
    $text = stripInvalidXml($text);

    try {
        $xml = new \SimpleXMLElement($text);
    } catch (\Exception $e) {
        $xml = FALSE;
    }

    if (empty($xml))
        return $arr;

    foreach ($xml as $key => $value) {
        if (is_array($value))
            continue;
        $arr[] = htmlentities($key) . ': ' . htmlentities($value);
    }

    return $arr;
}

use Symfony\Component\Process\Process;

function restartTraccar($reason)
{
    $process = new Process('service traccar restart');
    $process->run();

    while ($process->isRunning()) {
        // waiting for process to finish
    }

    if ($process->isSuccessful() && strpos($process->getOutput(), 'running: PID') !== false)
        return 'OK';


    $curl = new \Curl;
    $curl->follow_redirects = false;
    $curl->options['CURLOPT_SSL_VERIFYPEER'] = false;

    $response = $curl->post('http://hive.gpswox.com/servers/restart_traccar', [
        'admin_user' => $_ENV['admin_user'],
        'name' => $_ENV['server'],
        'reason' => $reason
    ]);

    return $response;
}

function parsePorts($ports = NULL)
{
    if (empty($ports)) {
        $curl = new \Curl;
        $curl->follow_redirects = false;
        $curl->options['CURLOPT_SSL_VERIFYPEER'] = false;

        $ports = json_decode($curl->get('http://hive.gpswox.com/ports/default'), TRUE);
    }
    $arr = [];
    foreach ($ports as $port)
        $arr[$port['name']] = $port;
    $ports = $arr;
    unset($arr);

    $cur_ports = json_decode(json_encode(DB::table('tracker_ports')->get()), TRUE);
    $arr = [];
    foreach ($cur_ports as $port) {
        if (!isset($ports[$port['name']])) {
            DB::table('tracker_ports')->where('name', '=', $port['name'])->delete();
            continue;
        }
        $arr[$port['name']] = $port;
    }
    $cur_ports = $arr;
    unset($arr);

    foreach ($ports as $port) {
        if (!isset($cur_ports[$port['name']])) {
            while (!empty(DB::table('tracker_ports')->where('port', '=', $port['port'])->first())) {
                $port['port']++;
            }
            DB::table('tracker_ports')->insert([
                'name' => $port['name'],
                'port' => $port['port'],
                'extra' => $port['extra']
            ]);
        } else {
            $extras = json_decode($port['extra'], TRUE);
            if (!empty($extras)) {
                $cur_extras = json_decode($cur_ports[$port['name']]['extra'], TRUE);
                $update = FALSE;
                foreach ($extras as $key => $value) {
                    if (!isset($cur_extras[$key])) {
                        $cur_extras[$key] = $value;
                        $update = TRUE;
                    }
                }

                if ($update) {
                    DB::table('tracker_ports')->where('name', '=', $port['name'])->update([
                        'extra' => json_encode($cur_extras)
                    ]);
                }
            }
        }
    }
}

function updateUsersBillingPlan($current_plan_id, $new_plan_id)
{
    $plan = \Tobuli\Entities\BillingPlan::find($new_plan_id);

    if ($plan) {
        $update = [
            'billing_plan_id' => $new_plan_id,
            'devices_limit' => $plan->objects,
            'subscription_expiration' => date('Y-m-d H:i:s', strtotime(date('Y-m-d H:i:s') . " + {$plan->duration_value} {$plan->duration_type}"))
        ];

        DB::table('users')
            ->whereNull('billing_plan_id')
            ->where('group_id', '=', 2)
            ->update($update);

    } else {
        DB::table('users')
            ->where('billing_plan_id', $current_plan_id)
            ->where('group_id', '=', 2)
            ->update([
                'billing_plan_id' => NULL,
            ]);
    }
}

function getManagerUsedLimit($manager_id, $except = NULL)
{
    $query = DB::table('users')
        ->where('manager_id', '=', $manager_id);

    if (!is_null($except))
        $query->where('id', '!=', $except);

    $users_limit = $query->sum('devices_limit');

    $manager_limit = DB::table('user_device_pivot')
        ->join('devices', function ($query) {
            $query->on('user_device_pivot.device_id', '=', 'devices.id');
            $query->where('devices.deleted', '=', '0');
        })
        ->where('user_device_pivot.user_id', '=', $manager_id)
        ->count();

    return $users_limit + $manager_limit;
}

function hasLimit()
{
    return (Auth::User()->isManager() && !is_null(Auth::User()->devices_limit));
}

function rtl($string, &$data)
{
    if ($data['format'] == 'pdf' && $data['lang'] == 'ar' && preg_match("/\p{Arabic}/u", $string))
        return $data['arabic']->utf8Glyphs($string);

    return $string;
}

class SettingsArray
{
    private $array = [];

    public function __construct(array $array)
    {
        $this->array = $array;
    }

    public function getArray()
    {
        return $this->array;
    }

    public function __get($name)
    {
        if (array_key_exists($name, $this->array))
            return $this->array[$name] == 1 ? TRUE : FALSE;

        return TRUE;
    }
}

function parseXMLToArray($text)
{
    $arr = [];
    try {
        $text = stripInvalidXml($text);
        $xml = new \SimpleXMLElement($text);
        foreach ($xml as $key => $value) {
            if (is_array($value))
                continue;
            $arr[htmlentities($key)] = htmlentities($value);
        }
    } catch (Exception $e) {
        $arr = parseInvalidXMLToArray($text);
    }

    return $arr;
}

function parseInvalidXMLToArray($text)
{
    $prefix = '_';
    $arr = [];
    try {
        $text = preg_replace('/<(\/?)([^<>]+)>/', '<$1' . $prefix . '$2>', $text);
        $xml = new \SimpleXMLElement($text);
        foreach ($xml as $key => $value) {
            if (is_array($value))
                continue;

            $key = ltrim($key, $prefix);

            $arr[htmlentities($key)] = htmlentities($value);
        }
    } catch (Exception $e) {
    }

    return $arr;
}

function smartPaginate($page, $total, $limit = 3)
{
    $arr = [1];

    if ($page < 1)
        $page = 1;

    if ($page > $total)
        $page = $total;

    if ($page - ($limit + 3) > 0) {
        $arr[] = '.';
        for ($i = $limit; $i > 0; $i--) {
            $arr[] = $page - $i;
        }
    } else {
        for ($i = 2; $i < $page; $i++) {
            $arr[] = $i;
        }
    }

    if ($page > 1)
        $arr[] = $page;

    if ($page + ($limit + 2) < $total) {
        for ($i = 1; $i <= $limit; $i++) {
            $arr[] = $page + $i;
        }
        $arr[] = '.';
    } else {
        for ($i = 1; $i < $total - $page; $i++) {
            $arr[] = $page + $i;
        }
    }

    if ($page < $total)
        $arr[] = $total;

    return $arr;
}

/**
 * @param array $array1
 * @param array $array2
 * @return array
 */
function array_merge_recursive_distinct(array &$array1, array &$array2)
{
    $merged = $array1;

    foreach ($array2 as $key => &$value) {
        if (is_array($value) && is_array_assoc($value) && isset ($merged [$key]) && is_array($merged [$key])) {
            $merged [$key] = array_merge_recursive_distinct($merged [$key], $value);
        } else {
            $merged [$key] = $value;
        }
    }

    return $merged;
}

function teltonikaIbutton($str)
{
    $str = dechex($str);
    if (!is_int(strlen($str) / 2))
        $str = '0' . $str;

    $arr = str_split(strrev($str), 2);
    $res = '';
    foreach ($arr as $item) {
        $res .= strrev($item);
    }

    return $res;
}

function listviewTrans($user_id, &$settings, &$fields)
{
    $fields_trans = config('tobuli.listview_fields_trans');
    $sensors_trans = config('tobuli.sensors');

    $sensors = \Facades\Repositories\DeviceSensorRepo::whereUserId($user_id);

    foreach ($sensors as $sensor) {
        $hash = $sensor->hash;

        $fields[$hash] = [
            'field' => $hash,
            'class' => 'sensor',
            'type' => $sensor->type,
            'name' => $sensor->name
        ];

        if (isset($settings['columns'])) {
            foreach ($settings['columns'] as &$column) {
                if ($column['field'] != $hash)
                    continue;

                $column['title'] = $sensor->name . " (" . $column['title'] . ")";
            }
        }
    }

    foreach ($fields as &$field) {
        if ($field['class'] == 'sensor') {
            $field['title'] = $field['name'] . " (" . $sensors_trans[$field['type']] . ")";
        } else {
            $field['title'] = $fields_trans[$field['field']];
        }

        $field['title'] = htmlentities($field['title'], ENT_QUOTES);
    }
}

function has_array_value($array, $keys)
{
    if (empty($keys))
        return true;

    $key = array_shift($keys);

    if (array_key_exists($key, $array))
        return has_array_value($array[$key], $keys);
    else
        return false;
}

function get_array_value($array, $keys)
{
    if (empty($keys))
        return $array;

    $key = array_shift($keys);

    if (array_key_exists($key, $array))
        return get_array_value($array[$key], $keys);
    else
        return null;
}

function set_array_value(&$array, $keys, $value)
{
    if (empty($keys))
        return $array = $value;

    if (is_null($array))
        $array = [];

    $key = array_shift($keys);

    if (!array_key_exists($key, $array))
        $array[$key] = null;

    return set_array_value($array[$key], $keys, $value);
}

function array_sort_array(array $array, array $orderArray)
{
    $ordered = array();

    foreach ($orderArray as $key) {
        if (array_key_exists($key, $array)) {
            $ordered[$key] = $array[$key];
            unset($array[$key]);
        }
    }

    return $ordered + $array;
}

function is_array_assoc(array $array)
{
    if (array() === $array)
        return false;

    return array_keys($array) !== range(0, count($array) - 1);
}


function semicol_explode($input)
{
    $values = explode(';', $input);
    $values = array_map('trim', $values);
    return array_filter($values, function ($value) {
        return !empty($value);
    });
}

function tooltipMark($content, $options = [])
{
    $defaults = [
        'toggle' => 'tooltip',
        'html' => true,
        'title' => $content
    ];

    $options = array_merge($defaults, $options);

    $attributes = '';

    foreach ($options as $key => $value)
        $attributes .= "data-$key='$value' ";

    return '<span class="tooltip-mark" ' . $attributes . '>?</span>';
}

function tooltipMarkImei($img, $text)
{
    $options = [
        'template' => '<div class="tooltip tooltip-imei" role="tooltip"><div class="tooltip-inner" style="background: url(' . $img . '); max-width: 360px; width: 360px; height: 196px;"></div></div>'
    ];

    return tooltipMark('<span class="text">' . $text . '</span>', $options);
}

function tooltipMarkImg($img)
{
    $options = [
        'template' => '<div class="tooltip tooltip-img" role="tooltip"><div class="tooltip-inner"></div></div>'
    ];

    return tooltipMark('<img src="' . $img . '"/>', $options);
}

function propertyPolicy($entity)
{
    return app(\App\PropertyPolicies\PropertyPolicyManager::class)->policyFor($entity);
}

function actionPolicy($action)
{
    return app(\App\ActionPolicies\ActionPolicyManager::class)
        ->policyFor($action);
}

function onlyEditables($entity, $user, $input)
{
    $not_editables = propertyPolicy($entity)->notEditables($user, $entity);

    if (empty($not_editables))
        return $input;

    foreach ($not_editables as $property) {
        if ( ! array_key_exists($property, $input))
            continue;

        $input[$property] = $entity->$property;
    }

    return $input;
}

function getUserDashboardSettings($user)
{
    if (empty($user_config = $user->getSettings('dashboard')))
        $user_config = [];

    $config = config('tobuli.dashboard');

    return array_merge_recursive_distinct($config, $user_config);
}

function degreesToDirection($degree)
{
    $directions = ["N", "NE", "E", "SE", "S", "SW", "W", "NW", "N"];

    return $directions[(($degree/45)) % 8];
}

function convertPHPToMomentFormat($format)
{
    $replacements = [
        'd' => 'DD',
        'D' => 'ddd',
        'j' => 'D',
        'l' => 'dddd',
        'N' => 'E',
        'S' => 'o',
        'w' => 'e',
        'z' => 'DDD',
        'W' => 'W',
        'F' => 'MMMM',
        'm' => 'MM',
        'M' => 'MMM',
        'n' => 'M',
        't' => '', // no equivalent
        'L' => '', // no equivalent
        'o' => 'YYYY',
        'Y' => 'YYYY',
        'y' => 'YY',
        'a' => 'a',
        'A' => 'A',
        'B' => '', // no equivalent
        'g' => 'h',
        'G' => 'H',
        'h' => 'hh',
        'H' => 'HH',
        'i' => 'mm',
        's' => 'ss',
        'u' => 'SSS',
        'e' => 'zz', // deprecated since version 1.6.0 of moment.js
        'I' => '', // no equivalent
        'O' => '', // no equivalent
        'P' => '', // no equivalent
        'T' => '', // no equivalent
        'Z' => '', // no equivalent
        'c' => '', // no equivalent
        'r' => '', // no equivalent
        'U' => 'X',
    ];
    $momentFormat = strtr($format, $replacements);
    return $momentFormat;
}

function getPointZones($geofences, $lat, $lng)
{
    $zones = [];

    if (is_null($geofences))
        return $zones;

    $point = $lat . ' ' . $lng;

    foreach ($geofences as $geofence) {
        if ( ! $geofence->pointIn($point))
            continue;

        $zones[] = $geofence->name;
    }

    return $zones;
}

function getSortedWeekdays()
{
    $weekdays = config('tobuli.weekdays');
    $day = auth()->user()->week_start_weekday;

    foreach ($weekdays as $weekday => $value) {
        if ($weekday == $day)
            break;

        array_pull($weekdays, $weekday);
        $weekdays = $weekdays + [$weekday => $value];
    }

    return $weekdays;
}

function googleMapLink($latitude, $longidute, $text = null)
{
    if (is_null($text))
        $text = "{$latitude}&deg;, {$longidute}&deg";

    $url = googleMapUrl($latitude, $longidute);

    return "<a href='{$url}' target='_blank'>{$text}</a>";
}

function googleMapUrl($latitude, $longidute)
{
    return "http://maps.google.com/maps?q={$latitude},{$longidute}&t=m";
}

function runCacheEntity($entityClass, $ids)
{
    $list = new \Illuminate\Database\Eloquent\Collection();

    if (empty($ids))
        return $list;

    if ( ! is_array($ids))
        $ids = [$ids];

    foreach ($ids as $id) {
        $entity = Cache::store('array')->rememberForever("$entityClass.$id", function() use ($entityClass, $id) {
            return $entityClass::find($id);
        });

        if ($entity)
            $list->add($entity);
    }

    return $list;
}

function groupDevices($devices, $user)
{
    $device_groups = ['0' => trans('front.ungrouped')] +
        Facades\Repositories\DeviceGroupRepo::getWhere(['user_id' => $user->id], 'title')
            ->lists('title', 'id')->all();

    $grouped = [];
    foreach ($devices as $device) {
        $group_id = (!is_null($device->pivot)) ? $device->pivot->group_id : $device->group_id;
        $group_id = (is_null($group_id) || (!array_has($device_groups, $group_id))) ? 0 : $group_id;
        $grouped[$device_groups[$group_id]][$device->id] = $device->name;
    }

    return $grouped;
}

function parseTagValue($string, $tag)
{
    $value = NULL;

    if (empty($tag))
        return $value;

    preg_match('/<' . preg_quote($tag, '/') . '>(.*?)<\/' . preg_quote($tag, '/') . '>/s', $string, $matches);
    if (isset($matches['1']))
        $value = $matches['1'];

    return $value;
}

function expensesTypesExist()
{
    $count = Illuminate\Support\Facades\Cache::get('expenses_types_count');

    if (is_null($count))
    {
        $count = \Tobuli\Entities\DeviceExpensesType::count();

        Illuminate\Support\Facades\Cache::put('expenses_types_count', $count, 1440);
    }

    if ($count > 0)
        return true;

    return false;
}

/**
 * Replaces new lines with specified value and trims string
 *
 * @param  String  $string   String to be modified
 * @param  String  $newValue Value to replace new lines
 * @return String
 */
function replaceNewlines($string, $newValue)
{
    return trim(str_replace(["\r\n", "\r", "\n"], $newValue, $string));
}

/**
 * Exports variable to string
 *
 * @param  Mixed   $var             Variable to export
 * @param  String  $replaceNewlines Value to replace newlines in strings
 * @param  String  $indent          Starting indentation
 * @return String
 */
function exportVar($var, $replaceNewlines = "\n", $indent = '')
{
    switch (gettype($var)) {
        case "string":
            return "'". addcslashes(replaceNewlines($var, $replaceNewlines), "'") . "'";
        case "array":
            $indexed = array_keys($var) === range(0, count($var) - 1);
            $r = [];

            foreach ($var as $key => $value) {
                $r[] = "$indent    "
                        . ($indexed ? "" : exportVar($key, $replaceNewlines) . " => ")
                        . exportVar($value, $replaceNewlines, "$indent    ");
            }

            return "[\n" . implode(",\n", $r) . ",\n" . $indent . "]";
        case "boolean":
            return $var ? "true" : "false";
        default:
            return var_export($var, true);
    }
}

/**
 * Converts dot notation array to multidimensional array
 *
 * @param  Array $array Dot notation array
 * @return Array
 */
function array_undot($array)
{
    $result = [];

    foreach ($array as $key => $value) {
        array_set($result, $key, $value);
    }

    return $result;
}

// only for positive values
function percentageChange($before, $after)
{
    if ($before < 0 || $after < 0) return null;

    $difference = $after - $before;

    $bigger_val = ($after > $before) ? $after : $before;

    return ($difference / abs($bigger_val)) * 100;
}

function getFuelDifference($device, $positions)
{
    $sensor = $device->getFuelTankSensor();

    if (is_null($sensor))
        throw new \Exception('Sensor not found.');

    $first = null;
    $last = null;

    foreach ($positions as $position) {
        if (is_null($first))
            $first = $sensor->getValue($position['other'], false, $last);

        $last = $sensor->getValue($position['other'], false, $last);
    }

    if ($first < 0 || $last < 0)
        throw new \Exception('Negative fuel value');

    $difference = $last - $first;

    $bigger = max($first, $last);

    return [
        'percent'    =>  ($difference / abs($bigger)) * 100,
        'difference' =>  $difference,
    ];
}

/**
 * Return device camera's image path
 *
 * @param string $path Path relative to images location
 * @return string
 */
function cameras_media_path($path = '')
{
    return str_finish(config('tobuli.media_path'), '/').($path ?? '');
}

function listsTranslations()
{
    Config::set('tobuli.plans', [
        '1' => trans('front.plan_1'),
        '5' => trans('front.plan_2'),
        '25' => trans('front.plan_3'),
        '29' => trans('front.plan_4'),
    ]);
    $sensors = [
        'satellites' => trans('front.satellites'),
        'gsm' => trans('front.gsm'),
        'engine' => trans('front.engine_on_off'),
        'acc' => trans('front.acc_on_off'),
        'door' => trans('front.door_on_off'),
        'seatbelt' => trans('front.seatbelt_on_off'),
        'battery' => trans('front.battery'),
        'fuel_tank' => trans('front.fuel_tank'),
        'fuel_tank_calibration' => trans('front.fuel_tank_calibration'),
        'temperature' => trans('front.temperature'),
        'temperature_calibration' => trans('front.temperature_calibration'),
        'odometer' => trans('front.odometer'),
        'tachometer' => trans('front.tachometer'),
        'ignition' => trans('front.ignition_on_off'),
        'engine_hours' => trans('validation.attributes.engine_hours'),
        'harsh_acceleration' => trans('front.harsh_acceleration'),
        'harsh_breaking' => trans('front.harsh_breaking'),
        'logical' => trans('front.logical'),
        'numerical' => trans('front.numerical'),
        'textual' => trans('front.textual'),
        'route_color' => trans('front.route_color'),
        'load' => trans('front.load'),
        'speed_ecm' => trans('front.speed') . ' ECM',
    ];
    if ( settings('plugins.business_private_drive.status') ) {
        $sensors['drive_business'] = trans('front.drive_business');
        $sensors['drive_private'] = trans('front.drive_private');
    }

    if ( settings('plugins.route_color.status') ) {
        $sensors['route_color'] = trans('front.route_color');
    }
    Config::set('tobuli.sensors', $sensors);


    Config::set('tobuli.units_of_distance', [
        'km' => trans('front.kilometer'),
        'mi' => trans('front.mile')
    ]);
    Config::set('tobuli.units_of_capacity', [
        'lt' => trans('front.liter'),
        'gl' => trans('front.gallon')
    ]);
    Config::set('tobuli.units_of_altitude', [
        'mt' => trans('front.meter'),
        'ft' => trans('front.feet')
    ]);
    Config::set('tobuli.object_online_timeouts', [
        '1' => '1 '.trans('front.minute_short'),
        '2' => '2 '.trans('front.minute_short'),
        '3' => '3 '.trans('front.minute_short'),
        '5' => '5 '.trans('front.minute_short'),
        '6' => '6 '.trans('front.minute_short'),
        '7' => '7 '.trans('front.minute_short'),
        '8' => '8 '.trans('front.minute_short'),
        '9' => '9 '.trans('front.minute_short'),
        '10' => '10 '.trans('front.minute_short'),
        '15' => '15 '.trans('front.minute_short'),
        '30' => '30 '.trans('front.minute_short'),
        '60' => '60 '.trans('front.minute_short'),
        '120' => '120 '.trans('front.minute_short'),
        '180' => '180 '.trans('front.minute_short'),
    ]);
    Config::set('tobuli.stops_minutes', [
        '1' => '> 1 '.trans('front.minute_short'),
        '2' => '> 2 '.trans('front.minute_short'),
        '3' => '> 3 '.trans('front.minute_short'),
        '4' => '> 4 '.trans('front.minute_short'),
        '5' => '> 5 '.trans('front.minute_short'),
        '10' => '> 10 '.trans('front.minute_short'),
        '15' => '> 15 '.trans('front.minute_short'),
        '20' => '> 20 '.trans('front.minute_short'),
        '30' => '> 30 '.trans('front.minute_short'),
        '60' => '> 1 '.trans('front.hour_short'),
        '120' => '> 2 '.trans('front.hour_short'),
        '300' => '> 5 '.trans('front.hour_short'),
    ]);

    Config::set('tobuli.listview_fields_trans', [
        'name' => trans('validation.attributes.name'),
        'imei' => trans('validation.attributes.imei'),
        'status' => trans('validation.attributes.status'),
        'address' => trans('front.address'),
        'protocol' => trans('front.protocol'),
        'position' => trans('front.position'),
        'time' => trans('admin.last_connection'),
        'sim_number' => trans('validation.attributes.sim_number'),
        'device_model' => trans('validation.attributes.device_model'),
        'plate_number' => trans('validation.attributes.plate_number'),
        'vin' => trans('validation.attributes.vin'),
        'registration_number' => trans('validation.attributes.registration_number'),
        'object_owner' => trans('validation.attributes.object_owner'),
        'additional_notes' => trans('validation.attributes.additional_notes'),
        'group' => trans('validation.attributes.group_id'),
        'speed' => trans('front.speed'),
        'fuel' => trans('front.fuel'),
        'route_color' => trans('front.route_color'),
        'stop_duration' => trans('front.stop_duration'),
        'idle_duration' => trans('front.idle_duration'),
        'ignition_duration' => trans('front.ignition_duration'),
        'last_event_type' => trans('front.last_event_type'),
        'last_event_time' => trans('front.last_event_time'),
    ]);

    $widgetsData = [
        'device' => trans('front.object'),
        'sensors' => trans('front.sensors'),
        'services' => trans('front.services'),
        'streetview' => trans('front.street_view'),
        'location' => trans('front.location'),
        'camera' => trans('front.device_camera'),
        'image' => trans('front.device_image'),
        'fuel_graph' => trans('front.fuel'),
    ];

    if (settings('plugins.locking_widget.status')) {
        $widgetsData['locking'] = trans('front.locking_widget');
    }

    Config::set('lists.widgets', $widgetsData);

    $minutes = [];
    for($i = 0; $i < 16; $i += 1){
        $minutes[$i] = $i . ' ' . trans('front.minute_short');
    }
    for($i = 15; $i < 65; $i += 5){
        $minutes[$i] = $i . ' ' . trans('front.minute_short');
    }
    Config::set('tobuli.minutes', $minutes);

    $format = settings('main_settings.default_time_format') == 'h:i:s A' ? 'h:i A' : 'H:i';
    //$format = 'h:i A';
    $date = Carbon::createMidnightDate();
    $times = [];
    for($i = 0; $i < 96; $i++){
        $times[$date->format('H:i')] = $date->format($format);
        $date->addMinutes(15);
    }
    Config::set('tobuli.history_time', $times);
}

/**
 * Get js settings
 *
 * @return Array
 */
function getJsConfig()
{
    $user = getActingUser();
    $hash = request()->route()->getParameter('hash');

    $checkUrl = $hash ? route('sharing.devices', ['hash' => $hash]) : route('objects.items_json');

    $googleQueryParam = [
        'key' => settings('main_settings.google_maps_key'),
        'language' => Language::iso()
    ];

    if (env('google_styled', false)) {
        $googleQueryParam['region'] = 'MA';
    }

    return [
            'debug' => false,
            'user_id' => $user->id,
            'version' => config('tobuli.version'),
            'offlineTimeout' => settings('main_settings.default_object_online_timeout') * 60,
            'checkFrequency' => config('tobuli.check_frequency'),
            'lang' => Language::get(),
            'show_object_info_after' => settings('plugins.show_object_info_after.status'),
            'object_listview' => settings('plugins.object_listview.status'),
            'channels' => [
                'chat' => md5('message_for_user_' . $user->id),
                'userChannel' => md5('user_'.$user->id),
            ],
            'settings' => [
                'units' => [
                    'speed'    => [
                        'unit' => Formatter::speed()->getUnit(),
                        'radio' => Formatter::speed()->getRatio()
                    ],
                    'distance' => [
                        'unit' => Formatter::distance()->getUnit(),
                        'radio' => Formatter::distance()->getRatio()
                    ],
                    'altitude' => [
                        'unit' => Formatter::altitude()->getUnit(),
                        'radio' => Formatter::altitude()->getRatio()
                    ],
                    'capacity' => [
                        'unit' => Formatter::capacity()->getUnit(),
                        'radio' => Formatter::capacity()->getRatio()
                    ],
                ],
                'timeFormat' => convertPHPToMomentFormat(settings('main_settings.default_time_format')),
                'dateFormat' => convertPHPToMomentFormat(settings('main_settings.default_date_format')),
                'weekStart' => $user->week_start_day,
                'mapCenter' => [
                    'lat' => floatval(settings('main_settings.map_center_latitude')),
                    'lng' => floatval(settings('main_settings.map_center_longitude'))
                ],
                'mapZoom' => settings('main_settings.map_zoom_level'),
                'map_id' => $user->map_id,
                'availableMaps' => $user->available_maps,
                'toggleSidebar' => false,
                'showTraffic' => false,
                'showTotalDistance' => settings('plugins.device_widget_total_distance.status'),
                'animateDeviceMove' =>  settings('plugins.device_move_animation.status'),
                'showDevice' => $user->map_controls->m_objects ? true : false,
                'showGeofences' => $user->map_controls->m_geofences ? true : false,
                'showRoutes' => $user->map_controls->m_routes ? true : false,
                'showPoi' => $user->map_controls->m_poi ? true : false,
                'showTail' => $user->map_controls->m_show_tails ? true : false,
                'showNames' => $user->map_controls->m_show_names ? true : false,
                'showHistoryRoute' => $user->map_controls->history_control_route  ? true : false,
                'showHistoryArrow' => $user->map_controls->history_control_arrows ? true : false,
                'showHistoryStop' => $user->map_controls->history_control_stops  ? true : false,
                'showHistoryEvent' => $user->map_controls->history_control_events ? true : false,
                'keys' => [
                    'google_maps_key' => settings('main_settings.google_maps_key'),
                    'here_map_id' => settings('main_settings.here_map_id'),
                    'here_map_code' => settings('main_settings.here_map_code'),
                    'mapbox_access_token' => settings('main_settings.mapbox_access_token'),
                    'bing_maps_key' => settings('main_settings.bing_maps_key'),
                    'maptiler_key' => settings('main_settings.maptiler_key')
                        ? settings('main_settings.maptiler_key')
                        : 'mmVVSrAOCSdk6P9IesR1',
                ],
                'googleQueryParam' => $googleQueryParam,
                'openmaptiles_url' => settings('main_settings.openmaptiles_url'),
                'showStreetView' => ( ! settings('main_settings.streetview_key') && (settings('main_settings.streetview_api') != 'default'))
                    ? false
                    : true,
            ],
            'urls' => [
                'asset' => asset(''),
                'streetView' => route('streetview'),
                'geoAddress' => route('api.geo_address'),

                'events' => route('events.index'),
                'eventDoDelete' => route('events.do_destroy'),

                'history' => route('history.index'),
                'historyExport' => route('history.export'),
                'historyPosition' => route('history.position'),
                'historyPositions' => route('history.positions'),
                'historyPositionsDelete' => route('history.delete_positions'),

                'check' => $checkUrl,
                'devices' => route('objects.items'),
                'deviceDelete' => route('objects.destroy'),
                'deviceChangeActive' => route('devices.change_active'),
                'deviceToggleGroup' => route('objects.change_group_status'),
                'deviceStopTime' => route('objects.stop_time').'/',
                'deviceFollow' => route('devices.follow_map').'/',
                'devicesSensorCreate' => route('sensors.create').'/',
                'devicesServiceCreate' => route('services.create').'/',
                'devicesServices' => route('services.index').'/',
                'devicesCommands' => route('devices.commands'),
                'deviceImages' => route('device_media.get_images').'/',
                'deviceImage' => route('device_media.get_image').'/',
                'deleteImage' => route('device_media.delete_image').'/',
                'deviceWidgetLocation' => route('device.widgets.location').'/',
                'deviceWidgetCameras' => route('device.widgets.cameras').'/',
                'deviceWidgetImage' => route('device.widgets.image').'/',
                'deviceWidgetUploadImage' => route('device.image_upload').'/',
                'deviceWidgetFuelGraph' => route('device.widgets.fuel_graph').'/',

                'geofences' => route('geofences.index'),
                'geofenceChangeActive' => route('geofences.change_active'),
                'geofenceDelete' => route('geofences.destroy'),
                'geofencesExportType' => route('geofences.export_type'),
                'geofencesImport' => route('geofences.import'),
                'geofenceToggleGroup' => route('geofences_groups.change_status'),

                'routes' => route('routes.index'),
                'routeChangeActive' => route('routes.change_active'),
                'routeDelete' => route('routes.destroy'),

                'alerts' => route('alerts.index'),
                'alertEdit' => route('alerts.edit'),
                'alertChangeActive' => route('alerts.change_active'),
                'alertDelete' => route('alerts.destroy'),
                'alertGetEvents' => route('custom_events.get_events'),
                'alertGetProtocols' => route('custom_events.get_protocols'),
                'alertGetEventsByDevice' => route('custom_events.get_events_by_device'),
                'alertGetCommands' => route('alerts.commands'),

                'mapIcons' => route('map_icons.index'),
                'mapIconsDelete' => route('map_icons.destroy'),
                'mapIconsChangeActive' => route('map_icons.change_active'),
                'mapIconsList' => route('map_icons.list'),

                'changeMap' => route('my_account.change_map'),
                'changeMapSettings' => route('my_account_settings.change_map_settings'),

                'clearQueue' => route('sms_gateway.clear_queue'),

                'listView' => route('objects.listview'),
                'listViewItems' => route('objects.listview.items'),

                'chatMessages' => route('chat.messages'),

                'dashboard' => route('dashboard'),
                'dashboardBlockContent' => route('dashboard.block_content'),

                'lockHistory' => route('lock_status.history').'/',
                'lockStatus' => route('lock_status.status').'/',
                'unlockLock' => route('lock_status.unlock').'/',

                'checklistUpdateRow' => route('checklists.update_row_status').'/',
                'checklistUploadFile' => route('checklists.upload_file').'/',
                'checklistSign' => route('checklists.sign_checklist').'/',
                'checklistGetRow' => route('checklists.get_row').'/',

                'deviceConfigApnData' => route('device_config.get_apn_data').'/',
            ],
        ];
}

/**
 * Set acting user
 *
 * @param User $user
 * @return void
 */
function setActingUser(User $user)
{
    App::instance('acting_user', $user);

    Formatter::byUser($user);
}

/**
 * Get acting user
 *
 * @return User
 */
function getActingUser()
{
    return App::bound('acting_user') ? App::make('acting_user') : null;
}

/**
 * Get period by phrase
 *
 * @param String $phrase
 * @return Array
 */
function getPeriodByPhrase($phrase)
{
    $endDate = \Carbon\Carbon::now()->endOfDay();
    $startDate = \Carbon\Carbon::now()->startOfDay();

    switch ($phrase) {
        case 'today':
            break;
        case 'yesterday':
            $endDate = $endDate->subDays(1);
            $startDate = $startDate->subDays(1);

            break;
        case 'this_week':
            $endDate = $endDate->endOfWeek();
            $startDate = $startDate->startOfWeek();

            break;
        case 'last_week':
            $endDate = $endDate->endOfWeek()->subWeeks(1);
            $startDate = $startDate->startOfWeek()->subWeeks(1);

            break;
        case 'this_month':
            $endDate = $endDate->endOfMonth();
            $startDate = $startDate->startOfMonth();

            break;
        case 'last_month':
            $endDate = $endDate->endOfMonth()->subMonths(1);
            $startDate = $startDate->startOfMonth()->subMonths(1);

            break;
        default:
            throw new \Tobuli\Exceptions\ValidationException([
                'phrase' => 'Unknown phrase: '.$phrase,
            ]);

            break;
    }

    return [
        'start' => $startDate,
        'end' => $endDate,
    ];
}

/**
 * Calculates DST date range for given DST on given time
 *
 * @param Any $DST  users_dst table object with joined from/to data from timezones_dst table
 * @param INT $time unix timestamp
 * @return void
 */
function calculateDSTRange($DST, $time = null)
{
    if (! empty($DST)) {
        if (! $time) {
            $time = time();
        }

        $year = date('Y', $time);

        if ($DST->type == 'automatic') {
            if (! empty($DST->from_period)) {
                $DST->date_from = date("m-d", strtotime($DST->from_period." ".$year)).' '.$DST->from_time;
                $DST->date_to = date("m-d", strtotime($DST->to_period." ".$year)).' '.$DST->to_time;
            }
        } elseif ($DST->type == 'other') {
            $dateFrom = "{$DST->week_pos_from} {$DST->week_day_from} of ".$DST->month_from." ".$year."";
            $dateTo = "{$DST->week_pos_to} {$DST->week_day_to} of ".$DST->month_to." ".$year."";

            $DST->date_from = date("m-d", strtotime($dateFrom)).' '.$DST->time_from;
            $DST->date_to = date("m-d", strtotime($dateTo)).' '.$DST->time_to;
        }
    }

    return $DST;
}

function getCompletionStatus($status = null)
{
    $statuses = [
        'all' => trans('front.all'),
        'complete' => trans('front.complete'),
        'incomplete' => trans('front.incomplete'),
    ];

    return $status ? $statuses[$status] : $statuses;
}

/**
 * Check if given string is base64 string
 *
 * @param String $string base64 string
 * @return boolean
 */
function isBase64($string)
{
    if (! is_string($string)) {
        return false;
    }

    if (strpos($string, ',') !== false) {
        $string = explode(',', $string);
        $string = $string[1];
    }

    return (bool) preg_match('/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', $string);
}

/**
 * Convert base64 string to UploadedFile object
 *
 * @param String $string base64 string
 * @return Symfony\Component\HttpFoundation\File\UploadedFile
 */
function base64ToImage($string)
{
    if (strpos($string, ',') === false) {
        return null;
    }

    list($imageType, $imageData) = explode(',', $string);
    preg_match("/data:image\/(.*?);/", $imageType, $imageType);
    $imageType = $imageType[1];

    $name = uniqid('image_');
    $path = sys_get_temp_dir()."/{$name}.{$imageType}";
    file_put_contents($path, base64_decode($imageData));

    return new \Symfony\Component\HttpFoundation\File\UploadedFile(
        $path,
        "{$name}.{$imageType}",
        File::mimeType($path),
        File::size($path),
        null,
        true);
}
