/*

    Copyright (C) 2001 The Kompany
		  2002-2003	Ilya Konstantinov <kde-devel@future.shiny.co.il>
		  2002-2003	Marcus Meissner <marcus@jet.franken.de>
		  2003		Nadeem Hasan <nhasan@nadmm.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

*/
#include <tqlayout.h>
#include <tqwidgetstack.h>
#include <tqvbuttongroup.h>
#include <tqvgroupbox.h>
#include <tqcombobox.h>
#include <tqlineedit.h>
#include <tqradiobutton.h>
#include <tqwhatsthis.h>
#include <tqlabel.h>
#include <tqgrid.h>

#include <klocale.h>
#include <tdeconfig.h>
#include <klistview.h>
#include <kmessagebox.h>
#include <kdebug.h>

#include "config.h"

extern "C" {
	#include <gphoto2.h>
}

#include "kamera.h"
#include "kameraconfigdialog.h"
#include "kameradevice.moc"

// Define some parts of the old API
#define GP_PROMPT_OK 0
#define GP_PROMPT_CANCEL -1

static const int INDEX_NONE= 0;
static const int INDEX_SERIAL = 1;
static const int INDEX_USB= 3;
static GPContext *glob_context = 0;

KCamera::KCamera(const TQString &name, const TQString &path)
{
	m_name	= name;
	m_model	= name;
	m_path	= path;
	m_camera = NULL;
}

KCamera::~KCamera()
{
	if(m_camera)
		gp_camera_free(m_camera);
	if(m_abilitylist)
		gp_abilities_list_free(m_abilitylist);
}

bool KCamera::initInformation()
{
	if (!m_model)
		return false;

	if(gp_abilities_list_new(&m_abilitylist) != GP_OK) {
		emit error(i18n("Could not allocate memory for abilities list."));
		return false;
	}
	if(gp_abilities_list_load(m_abilitylist, glob_context) != GP_OK) {
		emit error(i18n("Could not load ability list."));
		return false;
	}
	int index = gp_abilities_list_lookup_model(m_abilitylist, m_model.local8Bit().data());
	if(index < 0) {
		emit error(i18n("Description of abilities for camera %1 is not available."
					" Configuration options may be incorrect.").arg(m_model));
		return false;
	}
        gp_abilities_list_get_abilities(m_abilitylist, index, &m_abilities);
	return true;
}

bool KCamera::initCamera()
{
	if (m_camera)
		return m_camera;
	else {
		int result;

		initInformation();

		if (!m_model || !m_path)
			return false;

		result = gp_camera_new(&m_camera);
		if (result != GP_OK) {
			// m_camera is not initialized, so we cannot get result as string
			emit error(i18n("Could not access driver. Check your gPhoto2 installation."));
			return false;
		}

		// set the camera's model
		GPPortInfo info;
		GPPortInfoList *il;
		gp_port_info_list_new(&il);
		gp_port_info_list_load(il);
		gp_port_info_list_get_info(il, gp_port_info_list_lookup_path(il, m_path.local8Bit().data()), &info);
		gp_camera_set_abilities(m_camera, m_abilities);
		gp_camera_set_port_info(m_camera, info);
		gp_port_info_list_free(il);

		// this might take some time (esp. for non-existant camera) - better be done asynchronously
		result = gp_camera_init(m_camera, glob_context);
		if (result != GP_OK) {
			gp_camera_free(m_camera);
			m_camera = NULL;
			emit error(
				i18n("Unable to initialize camera. Check your port settings and camera connectivity and try again."),
				gp_result_as_string(result));
			return false;
		}

		return m_camera;
	}
}

Camera* KCamera::camera()
{
	initCamera();
	return m_camera;
}

TQString KCamera::summary()
{
	int result;
	CameraText	summary;

	initCamera();

	result = gp_camera_get_summary(m_camera, &summary, glob_context);
	if (result != GP_OK)
		return i18n("No camera summary information is available.\n");
	return TQString(summary.text);
}

bool KCamera::configure()
{
	CameraWidget *window;
	int result;

	initCamera();

	result = gp_camera_get_config(m_camera, &window, glob_context);
	if (result != GP_OK) {
		emit error(i18n("Camera configuration failed."), gp_result_as_string(result));
		return false;
	}

	KameraConfigDialog kcd(m_camera, window);
	result = kcd.exec() ? GP_PROMPT_OK : GP_PROMPT_CANCEL;

	if (result == GP_PROMPT_OK) {
		result = gp_camera_set_config(m_camera, window, glob_context);
		if (result != GP_OK) {
			emit error(i18n("Camera configuration failed."), gp_result_as_string(result));
			return false;
		}
	}

	return true;
}

bool KCamera::test()
{
	// TODO: Make testing non-blocking (maybe via KIO?)
	// Currently, a failed serial test times out at about 30 sec.
	return camera() != 0;
}

void KCamera::load(TDEConfig *config)
{
	config->setGroup(m_name);
	if (m_model.isNull())
		m_model = config->readEntry("Model");
	if (m_path.isNull())
		m_path = config->readEntry("Path");
	invalidateCamera();
}

void KCamera::save(TDEConfig *config)
{
	config->setGroup(m_name);
	config->writeEntry("Model", m_model);
	config->writeEntry("Path", m_path);
}

TQString KCamera::portName()
{
	TQString port = m_path.left(m_path.find(":")).lower();
	if (port == "serial") return i18n("Serial");
	if (port == "usb") return i18n("USB");
	return i18n("Unknown port");
}

void KCamera::setName(const TQString &name)
{
	m_name = name;
}

void KCamera::setModel(const TQString &model)
{
	m_model = model;
	invalidateCamera();
	initInformation();
}

void KCamera::setPath(const TQString &path)
{
	m_path = path;
	invalidateCamera();
}

void KCamera::invalidateCamera()
{
	if (m_camera) {
		gp_camera_free(m_camera);
		m_camera = NULL;
	}
}

bool KCamera::isTestable() const
{
	return true;
}

bool KCamera::isConfigurable()
{
	initInformation();
	return m_abilities.operations & GP_OPERATION_CONFIG;
}

TQStringList KCamera::supportedPorts()
{
	initInformation();
	TQStringList ports;
	if (m_abilities.port & GP_PORT_SERIAL)
		ports.append("serial");
	if (m_abilities.port & GP_PORT_USB)
		ports.append("usb");
	return ports;
}

CameraAbilities KCamera::abilities()
{
	return m_abilities;
}

// ---------- KameraSelectCamera ------------

KameraDeviceSelectDialog::KameraDeviceSelectDialog(TQWidget *parent, KCamera *device)
	: KDialogBase(parent, "kkameradeviceselect", true, i18n("Select Camera Device"), Ok | Cancel, Ok, true)
{
	m_device = device;
	connect(m_device, TQT_SIGNAL(error(const TQString &)),
		TQT_SLOT(slot_error(const TQString &)));
	connect(m_device, TQT_SIGNAL(error(const TQString &, const TQString &)),
		TQT_SLOT(slot_error(const TQString &, const TQString &)));

	TQWidget *page = new TQWidget( this );
	setMainWidget(page);

	// a layout with vertical boxes
	TQHBoxLayout *topLayout = new TQHBoxLayout(page, 0, KDialog::spacingHint());

	// the models list
	m_modelSel = new KListView(page);
	topLayout->addWidget( m_modelSel );
	m_modelSel->addColumn(i18n("Supported Cameras"));
	m_modelSel->setColumnWidthMode(0, TQListView::Maximum);
	connect(m_modelSel, TQT_SIGNAL(selectionChanged(TQListViewItem *)),
        TQT_SLOT(slot_setModel(TQListViewItem *)));
	// make sure listview only as wide as it needs to be
	m_modelSel->setSizePolicy(TQSizePolicy(TQSizePolicy::Maximum,
		TQSizePolicy::Preferred));

	TQVBoxLayout *rightLayout = new TQVBoxLayout(0L, 0, KDialog::spacingHint());
	topLayout->addLayout( rightLayout );

	m_portSelectGroup = new TQVButtonGroup(i18n("Port"), page);
	rightLayout->addWidget(m_portSelectGroup);
	m_portSettingsGroup = new TQVGroupBox(i18n("Port Settings"), page);
	rightLayout->addWidget(m_portSettingsGroup);

	// Create port type selection radiobuttons.
	m_serialRB = new TQRadioButton(i18n("Serial"), m_portSelectGroup);
	m_portSelectGroup->insert(m_serialRB, INDEX_SERIAL);
	TQWhatsThis::add(m_serialRB, i18n("If this option is checked, the camera would have to be connected one of the serial ports (known as COM in Microsoft Windows) in your computer."));
	m_USBRB = new TQRadioButton(i18n("USB"), m_portSelectGroup);
	m_portSelectGroup->insert(m_USBRB, INDEX_USB);
	TQWhatsThis::add(m_USBRB, i18n("If this option is checked, the camera would have to be connected to one of the USB slots in your computer or USB hub."));
	// Create port settings widget stack
	m_settingsStack = new TQWidgetStack(m_portSettingsGroup);
	connect(m_portSelectGroup, TQT_SIGNAL(clicked(int)),
		m_settingsStack, TQT_SLOT(raiseWidget(int)));

	// none tab
	m_settingsStack->addWidget(new TQLabel(i18n("No port type selected."),
		m_settingsStack), INDEX_NONE);

	// serial tab
	TQGrid *grid = new TQGrid(2, m_settingsStack);
	grid->setSpacing(KDialog::spacingHint());
	new TQLabel(i18n("Port:"), grid);
	m_serialPortCombo = new TQComboBox(TRUE, grid);
	TQWhatsThis::add(m_serialPortCombo, i18n("Here you should choose the serial port you connect the camera to."));
	m_settingsStack->addWidget(grid, INDEX_SERIAL);

	grid = new TQGrid(2, m_settingsStack);
	grid->setSpacing(KDialog::spacingHint());
	new TQLabel(i18n("Port"), grid);

	m_settingsStack->addWidget(new
		TQLabel(i18n("No further configuration is required for USB."),
		m_settingsStack), INDEX_USB);

	// query gphoto2 for existing serial ports
	GPPortInfoList *list;
	GPPortInfo info;
	int gphoto_ports=0;
	gp_port_info_list_new(&list);
	if(gp_port_info_list_load(list) >= 0) {
		gphoto_ports = gp_port_info_list_count(list);
	}
	for (int i = 0; i < gphoto_ports; i++) {
		if (gp_port_info_list_get_info(list, i, &info) >= 0) {
#ifdef HAVE_GPHOTO2_5
			char *xpath;
			gp_port_info_get_path (info, &xpath);
			if (strncmp(xpath, "serial:", 7) == 0)
				m_serialPortCombo->insertItem(TQString::fromLatin1(xpath).mid(7));
#else
			if (strncmp(info.path, "serial:", 7) == 0)
				m_serialPortCombo->insertItem(TQString::fromLatin1(info.path).mid(7));
#endif
		}
	}
	gp_port_info_list_free(list);

	// add a spacer
	rightLayout->addStretch();

	populateCameraListView();
	load();

	enableButtonOK(false );
    m_portSelectGroup->setEnabled( false );
    m_portSettingsGroup->setEnabled( false );
}

bool KameraDeviceSelectDialog::populateCameraListView()
{
	gp_abilities_list_new (&m_device->m_abilitylist);
	gp_abilities_list_load(m_device->m_abilitylist, glob_context);
	int numCams = gp_abilities_list_count(m_device->m_abilitylist);
	CameraAbilities a;

	if(numCams < 0) {
		// XXX libgphoto2 failed to get te camera list
		return false;
	} else {
		for(int x = 0; x < numCams; ++x) {
			if(gp_abilities_list_get_abilities(m_device->m_abilitylist, x, &a) == GP_OK) {
				new TQListViewItem(m_modelSel, a.model);
			}
		}
		return true;
	}
}

void KameraDeviceSelectDialog::save()
{
	m_device->setModel(m_modelSel->currentItem()->text(0));

	if (m_portSelectGroup->selected()) {
		TQString type = m_portSelectGroup->selected()->text();

		if(type == i18n("Serial"))
			m_device->setPath("serial:" + m_serialPortCombo->currentText());
		else if(type == i18n("USB"))
 			m_device->setPath("usb:");
 	} else {
 		// This camera has no port type (e.g. "Directory Browse" camera).
 		// Do nothing.
 	}
}

void KameraDeviceSelectDialog::load()
{
	TQString path = m_device->path();
	TQString port = path.left(path.find(":")).lower();

	if (port == "serial") setPortType(INDEX_SERIAL);
	if (port == "usb") setPortType(INDEX_USB);

	TQListViewItem *modelItem = m_modelSel->firstChild();
	if( modelItem)
	{
	do {
		if (modelItem->text(0) == m_device->model()) {
			m_modelSel->setSelected(modelItem, true);
			m_modelSel->ensureItemVisible(modelItem);
		}
	} while ( ( modelItem = modelItem->nextSibling() ) );
	}
}

void KameraDeviceSelectDialog::slot_setModel(TQListViewItem *item)
{
    enableButtonOK(true);
    m_portSelectGroup->setEnabled(true);
    m_portSettingsGroup->setEnabled(true);

    TQString model = item->text(0);

	CameraAbilities abilities;
	int index = gp_abilities_list_lookup_model(m_device->m_abilitylist, model.local8Bit().data());
	if(index < 0) {
		slot_error(i18n("Description of abilities for camera %1 is not available."
				" Configuration options may be incorrect.").arg(model));
	}
	int result = gp_abilities_list_get_abilities(m_device->m_abilitylist, index, &abilities);
	if (result == GP_OK) {
		// enable radiobuttons for supported port types
		m_serialRB->setEnabled(abilities.port & GP_PORT_SERIAL);
		m_USBRB->setEnabled(abilities.port & GP_PORT_USB);

		// turn off any selected port
		TQButton *selected = m_portSelectGroup->selected();
		if(selected != NULL)
			selected->toggle();

	        // if there's only one available port type, make sure it's selected
		if (abilities.port == GP_PORT_SERIAL)
			setPortType(INDEX_SERIAL);
		if (abilities.port == GP_PORT_USB)
			setPortType(INDEX_USB);
	} else {
		slot_error(i18n("Description of abilities for camera %1 is not available."
			     " Configuration options may be incorrect.").arg(model));
	}
}

void KameraDeviceSelectDialog::setPortType(int type)
{
	// Enable the correct button
	m_portSelectGroup->setButton(type);

	// Bring the right tab to the front
	m_settingsStack->raiseWidget(type);
}

void KameraDeviceSelectDialog::slot_error(const TQString &message)
{
	KMessageBox::error(this, message);
}

void KameraDeviceSelectDialog::slot_error(const TQString &message, const TQString &details)
{
	KMessageBox::detailedError(this, message, details);
}