--[[----------------------------------------------------------------------------

WFPerformanceLocalFileReportImp.lua

--------------------------------------------------------------------------------

ADOBE CONFIDENTIAL
------------------
Copyright 2016 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: All information contained herein is, and remain the property of
Adobe Systems Incorporated and its suppliers, if any. The intellectual and
technical concepts contained herein are proprietary to Adobe Systems
Incorporated and its suppliers and may be covered by U.S. and Foreign
Patents, patents in process, and are protected by trade secret or copyright
law.  Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

--------------------------------------------------------------------------------

This file should be accessed only by WFPerformanceReport.lua.

------------------------------------------------------------------------------]]

	-- RxLua
local Rx = require 'Rx'
local Observable = Rx.Observable

	-- WF.core.utils
local WFArguments = require 'WFArguments'
local WFCloseSupport = require 'WFCloseSupport'
local KSTableUtils = require "KSTableUtils"
local KSPathUtils = require 'KSPathUtils'
local WFCapabilities = require 'WFCapabilities'
local WFConfiguration = require 'WFConfiguration'
local KSIdGenerator = require 'KSIdGenerator'

	-- WF.core.os
local WFFileUtils = require 'WFFileUtils'
local WFPlatformInfo = require 'WFPlatformInfo'
local KSDate = require 'KSDate'

	-- WF.core.rxlua
local WFRxScheduler = require 'WFRxScheduler'

	-- WF.core.logging
local WFLogger = require 'WFLogger'

	-- WF.core.table
local WFTableArchiving = require 'WFTableArchiving'

	-- WF.web.json
local KSJsonEncoder = require 'KSJsonEncoder'

local ENABLE_SLEUTH = WFConfiguration.Shared.enableSleuth or false
--------------------------------------------------------------------------------

local check = assert( WFArguments.check )

local logger = WFLogger 'WFPerformanceReport'
:setLevel 'debug'

local empEncoder = WFTableArchiving.tableToString

local kBeginSequence = ''
local kEndSequence = ''
local kSeparateSequence = '\n'
local kMessageFormat = 'message %s'

if WFCapabilities.getCapability( 'WFPerformanceReportEncoding' ) == 'json' or 
	WFConfiguration.Stamina.EMPEncoding == 'json' then
	
	empEncoder = KSJsonEncoder.encodeOzUploadJson
	
	kBeginSequence = '['
	kEndSequence = ']'
	kSeparateSequence = ','
	kMessageFormat = '%s'
	
end

if WFCapabilities.getCapability( 'WFPerformanceReportEncoding' ) == 'splunk' or 
	WFConfiguration.Stamina.EMPEncoding == 'splunk' then

	empEncoder = function( value )
	
		local messages = value.messages
		
		local result = {}
		
		for i, v in ipairs( messages ) do
		
			local val = {}
			local t = ''
			
			for k, n in pairs( v.value ) do
				
				if k == 'time' then
					t = string.format( '%s="%s"', k, n )
				else
					table.insert( val, string.format( '%s="%s"', k, n ) )
				end
				
			end
			
			table.insert( result, string.format( '%s,key="%s",%s', t, v.key, table.concat( val, ',' ) ) )
			
		end
		
		local s = table.concat( result, '\n' )
		
		return s
		
	end
	
	kBeginSequence = ''
	kEndSequence = ''
	kSeparateSequence = '\n'
	kMessageFormat = '%s'
	
end

--============================================================================--

-- @mark WFPerformanceLocalFileReportImp class

--------------------------------------------------------------------------------

local function createPerformanceFile( self, fileName )

	fileName = fileName or KSIdGenerator.uuidInLowercaseHex()
	
	self._filepath = KSPathUtils.child( self._performanceReportFolder, fileName )
	
	if WFFileUtils.exists( self._filepath ) then
	
		pcall( WFFileUtils.deleteFile, self._filepath )
		
	end
	
	logger:infof( 'Opening file %s', self._filepath )
	
	self._cntMetricWrites = 0
	
	self._fd = io.open( self._filepath, 'a' )
	
	self._fd:write( kBeginSequence )
	
end

--------------------------------------------------------------------------------

local function closePerformanceFile( self )

	if self._fd then
	
		self:sendFormattedMessage( true )
		
		self._fd:write( kEndSequence )
		self._fd:close()
		
		self._fd = nil
		
	end
	
	self._perfReport = {}
	
end

--------------------------------------------------------------------------------

local function _recordSingleValue( self, key, value )

	if type( value ) == 'table' and not ENABLE_SLEUTH then
	
		value[ 'time' ] = KSDate.timeInIso8601Format( nil, false )
		value[ 'appSessionTimesatmp' ] = self._appSessionTimestamp
		value[ 'os' ] = WFPlatformInfo.getOS()
		value[ 'appDeviceClass' ] = WFPlatformInfo.getAppDeviceClass()
		-- TODO [bzibble 2017-12-21]: these don't appear to be implemented on Windows
		value[ 'build' ] = WFPlatformInfo.getBuild and WFPlatformInfo.getBuild()
		value[ 'appVersion' ] = WFPlatformInfo.getAppVersion and WFPlatformInfo.getAppVersion()
		value[ 'osVersion' ] = WFPlatformInfo.getOSVersion()
		value[ 'deviceModel' ] = WFPlatformInfo.getDeviceModel()
		value[ 'appIdentifier' ] = WFPlatformInfo.getAppIdentifier()
		
	end
	
	self._perfReport[ #self._perfReport + 1 ] = { key = key, value = value }
	
end

--------------------------------------------------------------------------------

local WFPerformanceLocalFileReportImp = {

	_perfReport = false,
	
	_fd = false,
	
	_filepath = false,
	
	_performanceReportFolder = false,
	
	_writeSubscription = false,
	
	_cntMetricWrites = false,
	
	_appSessionTimestamp = false,
	
	WFCloseSupport.addCloseSupportMethods,

}

--------------------------------------------------------------------------------

function WFPerformanceLocalFileReportImp:__init()

	check( 'WFPerformanceLocalFileReportImp' )

	self._perfReport = {}
	self._filepath = nil
	
	self._appSessionTimestamp = KSDate.timeInIso8601Format( nil, false )
	
	local basePath = KSPathUtils.getStandardFilePath( 'home' )
	basePath = KSPathUtils.child( basePath, "Library" )
	
	self._performanceReportFolder = basePath
	self._performanceReportFolder = KSPathUtils.child( self._performanceReportFolder, 
														"WFPerformanceReports" )
	
	WFFileUtils.mkdir( self._performanceReportFolder )
	
	self._writeSubscription = self:sendPerfData( 1 ):subscribe()
		
	WFCloseSupport.mustClose( self )
	
end

--------------------------------------------------------------------------------

function WFPerformanceLocalFileReportImp:close()

	check( 'WFPerformanceLocalFileReportImp' )
	
	closePerformanceFile( self )
	
	if self._writeSubscription then
		self._writeSubscription:dispose()
	end
	
end

--------------------------------------------------------------------------------

function WFPerformanceLocalFileReportImp:filepath()

	check( 'WFPerformanceLocalFileReportImp' )
	
	return Rx.Observable.just( self._filepath )
	
end

--------------------------------------------------------------------------------

function WFPerformanceLocalFileReportImp:newPerformanceReport( reportName )

	check( 'WFPerformanceLocalFileReportImp', '?string' )
	
	closePerformanceFile( self )
	
	createPerformanceFile( self, reportName )
	
	return Rx.Observable.empty()
	
end

--------------------------------------------------------------------------------

function WFPerformanceLocalFileReportImp:recordSingleValue( key, value )

	check( 'WFPerformanceLocalFileReportImp', '+string', 'table' )

	_recordSingleValue( self, key, value )

	return Observable.empty()

end

--------------------------------------------------------------------------------

function WFPerformanceLocalFileReportImp:clearPerfReport()

	check( 'WFPerformanceLocalFileReportImp' )
	
	self._perfReport = {}

	return Observable.empty()

end

--------------------------------------------------------------------------------

function WFPerformanceLocalFileReportImp:retrieve( optClear )

	check( 'WFPerformanceLocalFileReportImp', '?boolean' )

	local result = self._perfReport or {}

	if optClear then
		self._perfReport = {}
	end

	return Observable.just( result )

end

--------------------------------------------------------------------------------

function WFPerformanceLocalFileReportImp:sendFormattedMessage( optClear )

	local systemStats = WFPlatformInfo.getSystemStats()
	-- add in stats for GC
	--systemStats.gcElapsedTime = debug.GCElapsedTime()
	--debug.resetGCElapsedTime()
	
	_recordSingleValue( self, 'periodicStatsDump', systemStats )
	
	local tempCopyReport = { messages = KSTableUtils.shallowcopy( self._perfReport ), kind = "WFMessage" }
	
	if optClear then
		self._perfReport = {}
	end
	
	if next( tempCopyReport ) == nil then --don't write table if it is "empty"
		return Observable.empty()
	else
		
		if not self._fd then
		
			createPerformanceFile( self )
			
		end
		
		if self._fd then
		
			if self._cntMetricWrites > 0 then
				self._fd:write( kSeparateSequence )
			end
			
			
			self._fd:write( string.format( kMessageFormat, empEncoder( tempCopyReport ) ) )
			
			self._cntMetricWrites = self._cntMetricWrites + 1
			
			self._fd:flush()
			
			return Observable.just( true )
			
		end
		
		return Observable.just( false )
		
	end

end

--------------------------------------------------------------------------------

function WFPerformanceLocalFileReportImp:sendPerfData( interval )

	check( 'WFPerformanceLocalFileReportImp', 'number' )
	
	return Observable.timer( 0, interval, WFRxScheduler.sharedScheduler() )
		
		:flatMapLatest( function()
			
			return self:sendFormattedMessage( true )
			
		end )

end

--------------------------------------------------------------------------------

function WFPerformanceLocalFileReportImp:_sendPerfDataOnce( optClear )

	return self:sendFormattedMessage( optClear )
	
end

--------------------------------------------------------------------------------

return Rx.Class 'WFPerformanceLocalFileReportImp'( WFPerformanceLocalFileReportImp )
