/*-*-c++-*-
 * $Id$
 *
 * This file is part of plptools.
 *
 *  Copyright (C) 1999-2001 Fritz Elfert <felfert@to.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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "rpcs.h"
#include "bufferstore.h"
#include "ppsocket.h"
#include "bufferarray.h"
#include "psiprocess.h"
#include "Enum.h"

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

using namespace std;

ENUM_DEFINITION_BEGIN(rpcs::machs, rpcs::PSI_MACH_UNKNOWN)
    stringRep.add(rpcs::PSI_MACH_UNKNOWN,   N_("Unknown device"));
    stringRep.add(rpcs::PSI_MACH_PC,        N_("PC"));
    stringRep.add(rpcs::PSI_MACH_MC,        N_("MC"));
    stringRep.add(rpcs::PSI_MACH_HC,        N_("HC"));
    stringRep.add(rpcs::PSI_MACH_S3,        N_("Series 3"));
    stringRep.add(rpcs::PSI_MACH_S3A,       N_("Series 3a, 3c or 3mx"));
    stringRep.add(rpcs::PSI_MACH_WORKABOUT, N_("Workabout"));
    stringRep.add(rpcs::PSI_MACH_SIENNA,    N_("Sienna"));
    stringRep.add(rpcs::PSI_MACH_S3C,       N_("Series 3c"));
    stringRep.add(rpcs::PSI_MACH_S5,        N_("Series 5"));
    stringRep.add(rpcs::PSI_MACH_WINC,      N_("WinC"));
ENUM_DEFINITION_END(rpcs::machs)

ENUM_DEFINITION_BEGIN(rpcs::batterystates, rpcs::PSI_BATT_DEAD)
    stringRep.add(rpcs::PSI_BATT_DEAD,    N_("Empty"));
    stringRep.add(rpcs::PSI_BATT_VERYLOW, N_("Very Low"));
    stringRep.add(rpcs::PSI_BATT_LOW,     N_("Low"));
    stringRep.add(rpcs::PSI_BATT_GOOD,    N_("Good"));
ENUM_DEFINITION_END(rpcs::batterystates)


ENUM_DEFINITION_BEGIN(rpcs::languages, rpcs::PSI_LANG_TEST)
    stringRep.add(rpcs::PSI_LANG_TEST,  N_("Test"));
    stringRep.add(rpcs::PSI_LANG_en_GB, N_("English"));
    stringRep.add(rpcs::PSI_LANG_de_DE, N_("German"));
    stringRep.add(rpcs::PSI_LANG_fr_FR, N_("French"));
    stringRep.add(rpcs::PSI_LANG_es_ES, N_("Spanish"));
    stringRep.add(rpcs::PSI_LANG_it_IT, N_("Italian"));
    stringRep.add(rpcs::PSI_LANG_sv_SE, N_("Swedish"));
    stringRep.add(rpcs::PSI_LANG_da_DK, N_("Danish"));
    stringRep.add(rpcs::PSI_LANG_no_NO, N_("Norwegian"));
    stringRep.add(rpcs::PSI_LANG_fi_FI, N_("Finnish"));
    stringRep.add(rpcs::PSI_LANG_en_US, N_("American"));
    stringRep.add(rpcs::PSI_LANG_fr_CH, N_("Swiss French"));
    stringRep.add(rpcs::PSI_LANG_de_CH, N_("Swiss German"));
    stringRep.add(rpcs::PSI_LANG_pt_PT, N_("Portugese"));
    stringRep.add(rpcs::PSI_LANG_tr_TR, N_("Turkish"));
    stringRep.add(rpcs::PSI_LANG_is_IS, N_("Icelandic"));
    stringRep.add(rpcs::PSI_LANG_ru_RU, N_("Russian"));
    stringRep.add(rpcs::PSI_LANG_hu_HU, N_("Hungarian"));
    stringRep.add(rpcs::PSI_LANG_nl_NL, N_("Dutch"));
    stringRep.add(rpcs::PSI_LANG_nl_BE, N_("Belgian Flemish"));
    stringRep.add(rpcs::PSI_LANG_en_AU, N_("Australian"));
    stringRep.add(rpcs::PSI_LANG_fr_BE, N_("Belgish French"));
    stringRep.add(rpcs::PSI_LANG_de_AT, N_("Austrian"));
    stringRep.add(rpcs::PSI_LANG_en_NZ, N_("New Zealand English"));
    stringRep.add(rpcs::PSI_LANG_fr_CA, N_("Canadian French"));
    stringRep.add(rpcs::PSI_LANG_cs_CZ, N_("Czech"));
    stringRep.add(rpcs::PSI_LANG_sk_SK, N_("Slovak"));
    stringRep.add(rpcs::PSI_LANG_pl_PL, N_("Polish"));
    stringRep.add(rpcs::PSI_LANG_sl_SI, N_("Slovenian"));
ENUM_DEFINITION_END(rpcs::languages)

rpcs::~rpcs()
{
    skt->closeSocket();
}

//
// public common API
//
void rpcs::
reconnect(void)
{
    skt->reconnect();
    reset();
}

void rpcs::
reset(void)
{
    bufferStore a;
    mtCacheS5mx = 0;
    status = rfsv::E_PSI_FILE_DISC;
    a.addStringT(getConnectName());
    if (skt->sendBufferStore(a)) {
	if (skt->getBufferStore(a) == 1) {
	    if (!strcmp(a.getString(0), "Ok"))
		status = rfsv::E_PSI_GEN_NONE;
	}
    }
}

Enum<rfsv::errs> rpcs::
getStatus(void)
{
    return status;
}

const char *rpcs::
getConnectName(void)
{
    return "SYS$RPCS";
}

//
// protected internals
//
bool rpcs::
sendCommand(enum commands cc, bufferStore & data)
{
    if (status == rfsv::E_PSI_FILE_DISC) {
	reconnect();
	if (status == rfsv::E_PSI_FILE_DISC)
	    return false;
    }
    bool result;
    bufferStore a;
    a.addByte(cc);
    a.addBuff(data);
    result = skt->sendBufferStore(a);
    if (!result) {
	reconnect();
	result = skt->sendBufferStore(a);
	if (!result)
	    status = rfsv::E_PSI_FILE_DISC;
    }
    return result;
}

Enum<rfsv::errs> rpcs::
getResponse(bufferStore & data, bool statusIsFirstByte)
{
    Enum<rfsv::errs> ret;
    if (skt->getBufferStore(data) == 1) {
	if (statusIsFirstByte) {
	    ret = (enum rfsv::errs)((char)data.getByte(0));
	    data.discardFirstBytes(1);
	} else {
	    int l = data.getLen();
	    if (l > 0) {
		ret = (enum rfsv::errs)((char)data.getByte(data.getLen() - 1));
		data.init((const unsigned char *)data.getString(), l - 1);
	    } else
		ret = rfsv::E_PSI_GEN_FAIL;
	}
	return ret;
    } else
	status = rfsv::E_PSI_FILE_DISC;
    return status;
}

//
// APIs, identical on SIBO and EPOC
//
Enum<rfsv::errs> rpcs::
getNCPversion(int &major, int &minor)
{
    Enum<rfsv::errs> res;
    bufferStore a;

    if (!sendCommand(QUERY_NCP, a))
	return rfsv::E_PSI_FILE_DISC;
    if ((res = getResponse(a, true)) != rfsv::E_PSI_GEN_NONE)
	return res;
    if (a.getLen() != 2)
	return rfsv::E_PSI_GEN_FAIL;
    major = a.getByte(0);
    minor = a.getByte(1);
    return res;
}

Enum<rfsv::errs> rpcs::
execProgram(const char *program, const char *args)
{
    bufferStore a;

    a.addStringT(program);
    int l = strlen(program);
    for (int i = 127; i > l; i--)
	a.addByte(0);

    /**
    * This is a hack for the jotter app on mx5 pro. (and probably others)
    * Jotter seems to read it's arguments one char past normal apps.
    * Without this hack, The Drive-Character gets lost. Other apps don't
    * seem to be hurt by the additional blank.
    */
    a.addByte(strlen(args)+1);
    a.addByte(' ');

    a.addStringT(args);

    if (!sendCommand(EXEC_PROG, a))
	return rfsv::E_PSI_FILE_DISC;
    return getResponse(a, true);
}

Enum<rfsv::errs> rpcs::
stopProgram(const char *program)
{
    bufferStore a;

    a.addStringT(program);
    if (!sendCommand(STOP_PROG, a))
	return rfsv::E_PSI_FILE_DISC;
    return getResponse(a, true);
}

Enum<rfsv::errs> rpcs::
queryProgram(const char *program)
{
    bufferStore a;

    a.addStringT(program);
    if (!sendCommand(QUERY_PROG, a))
	return rfsv::E_PSI_FILE_DISC;
    return getResponse(a, true);
}

Enum<rfsv::errs> rpcs::
queryPrograms(processList &ret)
{
    bufferStore a;
    const char *drives;
    const char *dptr;
    bool anySuccess = false;
    Enum<rfsv::errs> res;

    // First, check how many drives we need to query
    a.addStringT("M:"); // Drive M only exists on a SIBO
    if (!sendCommand(rpcs::GET_UNIQUEID, a))
	return rfsv::E_PSI_FILE_DISC;
    if (getResponse(a, false) == rfsv::E_PSI_GEN_NONE)
	// A SIBO; Must query all possible drives
	drives = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    else
	// A Series 5; Query of C is sufficient
	drives = "C";

    dptr = drives;
    ret.clear();

    if ((mtCacheS5mx & 4) == 0) {
	Enum<machs> tmp;
	if (getMachineType(tmp) != rfsv::E_PSI_GEN_NONE)
	    return rfsv::E_PSI_GEN_FAIL;

    }
    if ((mtCacheS5mx & 9) == 1) {
	machineInfo tmp;
	if (getMachineInfo(tmp) == rfsv::E_PSI_FILE_DISC)
	    return rfsv::E_PSI_FILE_DISC;
    }
    bool s5mx = (mtCacheS5mx == 15);
    while (*dptr) {
	a.init();
	a.addByte(*dptr);
	if (!sendCommand(rpcs::QUERY_DRIVE, a))
	    return rfsv::E_PSI_FILE_DISC;
	if (getResponse(a, false) == rfsv::E_PSI_GEN_NONE) {
	    anySuccess = true;
	    int l = a.getLen();
	    while (l > 0) {
		const char *s;
		char *p;
		int pid;
		int sl;

		s = a.getString(0);
		sl = strlen(s) + 1;
		l -= sl;
		a.discardFirstBytes(sl);
		if ((p = strstr(s, ".$"))) {
		    *p = '\0'; p += 2;
		    sscanf(p, "%d", &pid);
		} else
		    pid = 0;
		PsiProcess proc(pid, s, a.getString(0), s5mx);
		ret.push_back(proc);
		sl = strlen(a.getString(0)) + 1;
		l -= sl;
		a.discardFirstBytes(sl);
	    }
	}
	dptr++;
    }
    if (anySuccess && !ret.empty())
	for (processList::iterator i = ret.begin(); i != ret.end(); i++) {
	    string cmdline;
	    if (getCmdLine(i->getProcId(), cmdline) == rfsv::E_PSI_GEN_NONE)
		i->setArgs(cmdline + " " + i->getArgs());
	}
    return anySuccess ? rfsv::E_PSI_GEN_NONE : rfsv::E_PSI_GEN_FAIL;
}

Enum<rfsv::errs> rpcs::
formatOpen(const char drive, int &handle, int &count)
{
    Enum<rfsv::errs> res;
    bufferStore a;

    a.addByte(toupper(drive));
    a.addByte(':');
    a.addByte(0);
    if (!sendCommand(FORMAT_OPEN, a))
	return rfsv::E_PSI_FILE_DISC;
    if ((res = getResponse(a, true)) != rfsv::E_PSI_GEN_NONE)
	return res;
    if (a.getLen() != 4)
	return rfsv::E_PSI_GEN_FAIL;
    handle = a.getWord(0);
    count = a.getWord(2);
    return res;
}

Enum<rfsv::errs> rpcs::
formatRead(int handle)
{
    bufferStore a;

    a.addWord(handle);
    if (!sendCommand(FORMAT_READ, a))
	return rfsv::E_PSI_FILE_DISC;
    return getResponse(a, true);
}

Enum<rfsv::errs> rpcs::
getUniqueID(const char *device, long &id)
{
    Enum<rfsv::errs> res;
    bufferStore a;

    a.addStringT(device);
    if (!sendCommand(GET_UNIQUEID, a))
	return rfsv::E_PSI_FILE_DISC;
    if ((res = getResponse(a, true)) != rfsv::E_PSI_GEN_NONE)
	return res;
    if (a.getLen() != 4)
	return rfsv::E_PSI_GEN_FAIL;
    id = a.getDWord(0);
    return res;
}

Enum<rfsv::errs> rpcs::
getOwnerInfo(bufferArray &owner)
{
    Enum<rfsv::errs> res;
    bufferStore a;

    if (!sendCommand(GET_OWNERINFO, a))
	return rfsv::E_PSI_FILE_DISC;
    if ((res = (enum rfsv::errs)getResponse(a, true)) != rfsv::E_PSI_GEN_NONE)
	return res;
    a.addByte(0);
    string s = a.getString(0);
    owner.clear();
    int p = 0;
    int l;
    while ((l = s.find('\006', p)) != s.npos) {
	bufferStore b;
	b.addStringT(s.substr(p, l - p).c_str());
	owner += b;
	p = l + 1;
    }
    if (s.substr(p).length()) {
	bufferStore b;
	b.addStringT(s.substr(p).c_str());
	owner += b;
    }
    return res;
}

Enum<rfsv::errs> rpcs::
getMachineType(Enum<machs> &type)
{
    Enum<rfsv::errs> res;
    bufferStore a;

    if (!sendCommand(GET_MACHINETYPE, a))
	return rfsv::E_PSI_FILE_DISC;
    if ((res = getResponse(a, true)) != rfsv::E_PSI_GEN_NONE)
	return res;
    if (a.getLen() != 2)
	return rfsv::E_PSI_GEN_FAIL;
    type = (enum machs)a.getWord(0);
    mtCacheS5mx |= 4;
    if (res == rfsv::E_PSI_GEN_NONE) {
	if (type == rpcs::PSI_MACH_S5)
	    mtCacheS5mx |= 1;
    }
    return res;
}

Enum<rfsv::errs> rpcs::
fuser(const char *name, char *buf, int maxlen)
{
    Enum<rfsv::errs> res;
    bufferStore a;
    char *p;

    a.addStringT(name);
    if (!sendCommand(FUSER, a))
	return rfsv::E_PSI_FILE_DISC;
    if ((res = getResponse(a, true)) != rfsv::E_PSI_GEN_NONE)
	return res;
    strncpy(buf, a.getString(0), maxlen - 1);
    while ((p = strchr(buf, 6)))
	*p = '\0';
    return res;
}

Enum<rfsv::errs> rpcs::
quitServer(void)
{
    bufferStore a;
    if (!sendCommand(QUIT_SERVER, a))
	return rfsv::E_PSI_FILE_DISC;
    return getResponse(a, true);
}

/*
 * Local variables:
 * c-basic-offset: 4
 * End:
 */