//------------------------------------------------------------------------------
// <copyright file="ConnectionManagerChannel.cpp" company="LiquidRobotics">
//
// Copyright (c) 2014 Liquid Robotics, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// This software is associated with a release.  Liquid Robotics makes no
// representation that the design, interfaces or component distribution will
// remain unchanged in future releases.
//
// </copyright>
//
// <summary>
// 	Implementation of ConnectionManagerChannel class, a serializable entity that
//  describes a Channel, or a communications transport mechanism
// </summary>
//
// <owner>Arash Ushani</owner>
//------------------------------------------------------------------------------

#include "LqrSMACLogger.h"
#include "ConnectionManagerChannel.h"
#include <boost/algorithm/string.hpp>
#include <fstream>

using namespace std;
using namespace boost;
using namespace LiquidRobotics::SMACFramework;

namespace LiquidRobotics {
namespace ConnectionManager {

ConnectionManagerChannel::ConnectionManagerChannel(const ConnectionManagerChannelSettings& _settings, Logger::Ptr _logger)
: settings(_settings)
, logger(_logger)
{
	signalStrengthSignalPtr.reset(new SignalStrengthSignalType());
	channelMutex.reset(new Mutex());
}

ConnectionManagerChannel::ConnectionManagerChannel(const ConnectionManagerChannel& rhs)
: settings(rhs.settings)
, logger(rhs.logger)
{
	signalStrengthSignalPtr.reset(new SignalStrengthSignalType());
	channelMutex.reset(new Mutex());
}

int ConnectionManagerChannel::GetProtocolRevision()
{
	return settings.GetProtocolRevision();
}

bool ConnectionManagerChannel::ChannelOn()
{
	int status = 1; // will be reset to 0 if no error with system()

//	Logger::Locked(logger)->Debug << Describe() << " Destructor..." << endl;
	Logger::Locked(logger)->Info << "ConnectionManagerChannel::ChannelOn: Waiting 2 Seconds Before Turning Channel " << GetProtocolRevision() << " On" << endl;
	boost::this_thread::sleep(boost::posix_time::milliseconds(2000));
	Logger::Locked(logger)->Info << "ConnectionManagerChannel::ChannelOn: Turning Channel " << GetProtocolRevision() << " On" << endl;

	try
	{
		std::string powerOnVerb = settings.GetPowerOnVerb();

		trim(powerOnVerb);
		if (!powerOnVerb.empty())
		{
			const char * script = powerOnVerb.c_str();
			status = std::system(script);
		}
		else
			status = 0;
	}
	catch(std::exception& ex)
	{
		Logger::Locked(logger)->Info << "Caught exception: " << typeid(ex).name() << ", " << ex.what() << endl;
	}
	catch(...)
	{
		Logger::Locked(logger)->Info << "Caught exception." << endl;
	}

	if (status==0)
	{
		if (ReportsSignalStrength())
		{
			thread = boost::thread(boost::bind(&ConnectionManagerChannel::SignalStrengthThread, this));
		}
	}

	return status==0;
}

bool ConnectionManagerChannel::ChannelOff()
{
	int status = 1; // will be reset to 0 if no error with system()

	Logger::Locked(logger)->Info << "Turning Channel " << GetProtocolRevision() << " Off" << endl;

	try
	{
		std::string powerOffVerb = settings.GetPowerOffVerb();

		trim(powerOffVerb);
		if (!powerOffVerb.empty())
		{
			const char * script = powerOffVerb.c_str();
			status = std::system(script);
		}
		else
			status = 0;
	}
	catch(std::exception& ex)
	{
		Logger::Locked(logger)->Info << "Caught exception: " << typeid(ex).name() << ", " << ex.what() << endl;
	}
	catch(...)
	{
		Logger::Locked(logger)->Info << "Caught exception." << endl;
	}

	if (status == 0) {
		Logger::Locked(logger)->Info << "Calling thread.interrupt()" << endl;
		thread.interrupt();
	}

	return status==0;
}

bool ConnectionManagerChannel::ReportsSignalStrength()
{
	return !settings.GetSignalStrengthVerb().empty();
}

void ConnectionManagerChannel::SignalStrengthThread()
{

	boost::thread::id threadId = boost::this_thread::get_id();
	Logger::Locked(logger)->Info << "ConnectionManagerChannel::SignalStrengthThread: <enter id=" << threadId << ">" << endl;

	while (true)
	{
//		boost::this_thread::sleep(boost::posix_time::milliseconds(15000));
//TODO: this "may" need to be a configuration value.
		try {
			boost::this_thread::sleep(boost::posix_time::milliseconds(10000));
		}
		catch (boost::thread_interrupted&) {
			Logger::Locked(logger)->Info << "Caught thread interrupt exception <" << threadId << ">" << endl;
			break;
		}

		int strength = GetSignalStrength();

		if (strength!=-1)
		{
//			Logger::Locked(logger)->Info << "Signal Strength is: " << strength << " bars" << endl;
			Logger::Locked(logger)->Info << "ConnectionManagerChannel::SignalStrengthThread: Signal Strength is: " << std::dec << strength << endl;
			(*signalStrengthSignalPtr)(*this, strength);
		} else {
			Logger::Locked(logger)->Info << "ConnectionManagerChannel::SignalStrengthThread: is -1" << endl;
		}
	}

	Logger::Locked(logger)->Info << "SignalStrengthThread(): <exit>" << endl;
}

int ConnectionManagerChannel::GetSignalStrength()
{
	// Run script to get cell signal strength

	int status = 1; // will be reset to 0 if no error with system()

	try
	{
		std::string getSignalStrength = settings.GetSignalStrengthVerb();

		// Append filename to command
		stringstream ss;
		ss << getSignalStrength << " " << settings.GetSignalStrengthFilename();

		std::string cmd = ss.str();

		trim(cmd);
		if (!cmd.empty())
		{
			const char * script = cmd.c_str();
			status = std::system(script);
		}
	}
	catch(std::exception& ex)
	{
		Logger::Locked(logger)->Info << "Caught exception: " << typeid(ex).name() << ", " << ex.what() << endl;
		return -1;
	}
	catch(...)
	{
		Logger::Locked(logger)->Info << "Caught exception." << endl;
		return -1;
	}

	if (status!=0)
		return -1;


	// Get last Signal Strength, indicated by "Signal Strength: XXXX"

	int strength = -1;

	ifstream signalStrengthFile (settings.GetSignalStrengthFilename().c_str());
	if (signalStrengthFile.is_open())
	{
		// Only keep the most recent value in the signal strength file.
//		signalStrengthFile.seekg(-100, ios::end);
		while (signalStrengthFile.good())
		{
			string line;
			getline(signalStrengthFile, line);

			string seek = "Signal Strength: ";

			size_t index = line.find(seek);

			if (index!=string::npos)
			{
				string val = line.substr(index + seek.length());
				strength = atoi(val.c_str());
				break;
			}
		}
	}

	return strength;
}

ConnectionManagerChannel::ScopedConnectionPtr ConnectionManagerChannel::SubscribeToSignalStrength(SignalStrengthSignalType::slot_type slot)
{
	ScopedConnectionPtr signalStrengthConnectionPtr;
	signalStrengthConnectionPtr.reset(new ScopedConnection(signalStrengthSignalPtr->connect(slot)));
	return signalStrengthConnectionPtr;
}

void ConnectionManagerChannel::lock()
{
	if (channelMutex)
		channelMutex.reset(new Mutex());

	channelMutex->lock();
}

bool ConnectionManagerChannel::try_lock()
{
	if (!channelMutex)
		channelMutex.reset(new Mutex());

	return channelMutex->try_lock();
}

void ConnectionManagerChannel::unlock()
{
	if (!channelMutex)
		channelMutex.reset(new Mutex());

	channelMutex->unlock();
}

}
}
