//------------------------------------------------------------------------------
// <copyright file="LqrSMACDetermineFileExtension.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 functions to determine the file extension to use for a
//   given blob or file.
// </summary>
//
// <owner>Mike Cookson</owner>
//------------------------------------------------------------------------------

#include "LqrSMACDetermineFileExtension.h"
#include "LqrSMACTempFile.h"
#include <ctype.h>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <sstream>
#include <boost/algorithm/string.hpp>

using namespace std;
using namespace boost;

namespace LiquidRobotics {
namespace SMACFramework {

static const string defaultExtension = ".BIN";
static const string bzip2Ext = ".BZ2";
static const string gzipExt = ".GZ";
static const string tarExt = ".TAR";
static const string tarBZ2Ext = ".TBZ";
static const string tarGZExt = ".TGZ";
static const string tarLZMAExt = ".TXZ";
static const string lzmaExt = ".XZ";

string DetermineFileExtension(const ByteArray& content)
{
	string result = defaultExtension;
	TempFilePtr tempFile;

	try
	{
		tempFile.reset(new TempFile());

		if (tempFile->IsOpen())
		{
			tempFile->Write(content);
			tempFile->Close();
		}
	}
	catch(...)
	{
	}

	if (tempFile)
		result = DetermineFileExtension(tempFile->GetName());

	return result;
}

static void LZMAWorkaround(const std::string& fileName, std::string& fileCommandOutput)
{
	//cout << " LZMAWorkaround fileName=" << fileName << " fileCommandOutput=" << fileCommandOutput << " ";
	if (!fileCommandOutput.compare("data"))
	{
		TempFilePtr tf;

		try
		{
			tf.reset(new TempFile());

			if (tf->IsOpen())
			{
				string tfName = tf->GetName();

				if (DecompressLZMA(fileName, tfName))
				{
					if (IsTarFormat(tfName))
						fileCommandOutput = "tar lzma";
					else
						fileCommandOutput = "lzma";
				}
			}
		}
		catch(...)
		{
		}
	}
}

static void OtherTextFilesWorkaround(string& result, const string& fileName)
{
	if (!result.compare(".BIN") && IsASCIIFormat(fileName))
		result = ".TXT";
}

string DetermineFileExtension(const string& fileName)
{
	string fileCommandResult = DetermineFileExtensionDetail::RunFileCommand(fileName);
	trim(fileCommandResult);
	LZMAWorkaround(fileName, fileCommandResult);
	string result = DetermineFileExtensionFromFileCommandResult(fileCommandResult);
	OtherTextFilesWorkaround(result, fileName);
	return result;
}


typedef vector<string> StringVector;

static bool ScanForToken(const StringVector& tokens, const string& token)
{
	using namespace boost;
	bool found = false;

	for (StringVector::const_iterator it = tokens.begin(); !found && (it != tokens.end()); ++it)
		found = iequals(*it, token);

	return found;
}

static void SplitTokens(const string& fileCommandResult, StringVector& tokens)
{
	using namespace boost;
	split(tokens, fileCommandResult, is_any_of(" \t(),:[]{}"), token_compress_on);
}

string DetermineFileExtensionFromFileCommandResult(const string& fileCommandResult)
{
	string result = ".BIN";
	StringVector items;
	SplitTokens(fileCommandResult, items);

	if (ScanForToken(items, "tar"))
	{
		if (ScanForToken(items, "gzip"))
			result = tarGZExt;
		else if (ScanForToken(items, "bzip2"))
			result = tarBZ2Ext;
		else if (ScanForToken(items, "lzma"))
			result = tarLZMAExt;
		else
			result = tarExt;
	}
	else if (ScanForToken(items, "gzip"))
		result = gzipExt;
	else if (ScanForToken(items, "bzip2"))
		result = bzip2Ext;
	else if (ScanForToken(items, "lzma"))
		result = lzmaExt;
	else if (ScanForToken(items, "zip"))
		result = ".ZIP";
	else if (ScanForToken(items, "jpg") || ScanForToken(items, "jpeg"))
		result = ".JPG";
	else if (ScanForToken(items, "gif"))
		result = ".GIF";
	else if (ScanForToken(items, "png"))
		result = ".PNG";
	else if (ScanForToken(items, "tif") || ScanForToken(items, "tiff"))
		result = ".TIF";
	else if (ScanForToken(items, "bitmap"))
		result = ".BMP";
	else if (ScanForToken(items, "image"))
		result = ".IMG";
	else if (ScanForToken(items, "text") || ScanForToken(items, "ascii"))
		result = ".TXT";

	return result;
}

bool IsTarFormat(const ByteArray& content)
{
	bool result = false;
	TempFilePtr tempFile;

	try
	{
		tempFile.reset(new TempFile());

		if (tempFile->IsOpen())
		{
			tempFile->Write(content);
			tempFile->Close();
		}
	}
	catch(...)
	{
	}

	if (tempFile)
		result = IsTarFormat(tempFile->GetName());

	return result;
}

bool IsTarFormat(const string& fileName)
{
	stringstream cmdLine;
	cmdLine << "tar tf '" << fileName << "' 1>/dev/null 2>/dev/null";

	string cmdLineStr = cmdLine.str();
	int systemStatus = system(cmdLineStr.c_str());
	bool exited = WIFEXITED(systemStatus);
	int retCode = WEXITSTATUS(systemStatus);

	return exited && !retCode;
}

bool IsValidBZip2(const ByteArray& content)
{
	bool result = false;
	TempFilePtr tempFile;

	try
	{
		tempFile.reset(new TempFile());

		if (tempFile->IsOpen())
		{
			tempFile->Write(content);
			tempFile->Close();
		}
	}
	catch(...)
	{
	}

	if (tempFile)
		result = IsValidBZip2(tempFile->GetName());

	return result;
}

bool IsValidBZip2(const string& fileName)
{
	stringstream cmdLine;
	cmdLine << "bzip2 -t '" << fileName << "' 1>/dev/null 2>/dev/null";

	string cmdLineStr = cmdLine.str();
	int systemStatus = system(cmdLineStr.c_str());
	bool exited = WIFEXITED(systemStatus);
	int retCode = WEXITSTATUS(systemStatus);

	return exited && !retCode;
}

bool IsValidGZip(const ByteArray& content)
{
	bool result = false;
	TempFilePtr tempFile;

	try
	{
		tempFile.reset(new TempFile());

		if (tempFile->IsOpen())
		{
			tempFile->Write(content);
			tempFile->Close();
		}
	}
	catch(...)
	{
	}

	if (tempFile)
		result = IsValidGZip(tempFile->GetName());

	return result;
}

bool IsValidGZip(const string& fileName)
{
	stringstream cmdLine;
	cmdLine << "gzip -t '" << fileName << "' 1>/dev/null 2>/dev/null";

	string cmdLineStr = cmdLine.str();
	int systemStatus = system(cmdLineStr.c_str());
	bool exited = WIFEXITED(systemStatus);
	int retCode = WEXITSTATUS(systemStatus);

	return exited && !retCode;
}

bool IsValidLZMA(const ByteArray& content)
{
	bool result = false;
	TempFilePtr tempFile;

	try
	{
		tempFile.reset(new TempFile());

		if (tempFile->IsOpen())
		{
			tempFile->Write(content);
			tempFile->Close();
		}
	}
	catch(...)
	{
	}

	if (tempFile)
		result = IsValidLZMA(tempFile->GetName());

	return result;
}

bool IsValidLZMA(const std::string& fileName)
{
	stringstream cmdLine;
	cmdLine << "lzma -t '" << fileName << "' 1>/dev/null 2>/dev/null";

	string cmdLineStr = cmdLine.str();
	int systemStatus = system(cmdLineStr.c_str());
	bool exited = WIFEXITED(systemStatus);
	int retCode = WEXITSTATUS(systemStatus);

	return exited && !retCode;
}

bool DecompressLZMA(const std::string& inputFile, const std::string& outputFile)
{
	stringstream cmdLine;
	cmdLine << "lzma -d < '" << inputFile << "' >> '" << outputFile << "' 2>/dev/null";

	string cmdLineStr = cmdLine.str();
	//cout << " cmdline=\"" << cmdLineStr << "\" ";
	int systemStatus = system(cmdLineStr.c_str());
	bool exited = WIFEXITED(systemStatus);
	int retCode = WEXITSTATUS(systemStatus);
	bool result = exited && !retCode;
	//cout << " result=" << result << " ";

	return result;
}

bool IsASCIIFormat(const ByteArray& content)
{
	bool isASCII = !!content.size();

	for (ByteArray::const_iterator ci = content.begin(); isASCII && (ci != content.end()); ++ci)
		isASCII = isprint(*ci) || isspace(*ci);

	return isASCII;
}

bool IsASCIIFormat(const std::string& fileName)
{
	bool isASCII = true;
	bool isEmpty = true;

	try
	{
		ifstream stm(fileName.c_str(), ios::in|ios::binary);
		int c;

		if (stm.is_open())
			while(c = stm.get(), isASCII && stm.good())
			{
				isEmpty = false;
				isASCII = isspace(c) || isprint(c);
			}
	}
	catch(...)
	{
		isASCII = false;
	}

	return isASCII && !isEmpty;
}

namespace DetermineFileExtensionDetail {

string RunFileCommand(const std::string& fileName, bool mime)
{
	stringstream stm;
	stm << "file -" << (mime ? "i" : "z") << " '" << fileName << "'";

	string cmd = stm.str();

	FILE* fp = popen(cmd.c_str(), "r");
	string result;
	stringstream resultStm;

	if (fp)
	{
		char c;

		while(c = fgetc(fp), !ferror(fp) && !feof(fp))
			resultStm << c;

		pclose(fp);
		result = resultStm.str();
	}

	size_t fileNameLengthPlusColon = fileName.length()+1;

	if (result.length() >= fileNameLengthPlusColon)
		result.erase(result.begin(), result.begin()+fileNameLengthPlusColon);

	//cout << " RunFileCommand=\"" << result << "\" ";

	return result;
}

}

}
}
