package com.robonobo.core.wang;
import static com.robonobo.common.util.TimeUtil.*;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.robonobo.common.concurrent.CatchingRunnable;
import com.robonobo.common.exceptions.SeekInnerCalmException;
import com.robonobo.core.api.*;
import com.robonobo.core.api.model.User;
import com.robonobo.core.api.proto.CoreApi.Node;
import com.robonobo.core.service.AbstractService;
import com.robonobo.core.service.TaskService;
import com.robonobo.wang.WangException;
import com.robonobo.wang.beans.CoinList;
import com.robonobo.wang.client.WangClient;
import com.robonobo.wang.proto.WangProtocol.CoinListMsg;
public class WangService extends AbstractService implements CurrencyClient {
RobonoboWangConfig config;
WangClient client;
Log log = LogFactory.getLog(getClass());
double cachedBankBalance = 0;
Date nextCheckBalanceTime = new Date(0);
boolean clientStarted = false;
TaskService tasks;
public WangService() {
addHardDependency("core.tasks");
addHardDependency("core.http");
}
public String getName() {
return "Wang banking service";
}
public String getProvides() {
return "core.wang";
}
@Override
public void startup() throws Exception {
tasks = rbnb.getTaskService();
config = (RobonoboWangConfig) rbnb.getConfig("wang");
File coinStoreDir = new File(rbnb.getHomeDir(), "coins");
coinStoreDir.mkdirs();
config.setCoinStoreDir(coinStoreDir.getAbsolutePath());
}
private class StartClientTask extends Task {
public StartClientTask() {
title = "Starting wang currency client";
}
@Override
public void runTask() throws Exception {
if (client != null)
client.stop();
User me = rbnb.getUserService().getMyUser();
config.setAccountEmail(me.getEmail());
config.setAccountPwd(me.getPassword());
statusText = "Initializing client";
fireUpdated();
client = new WangClient(config, rbnb.getHttpService().getClient());
client.start();
clientStarted = true;
completion = 0.5f;
statusText = "Updating account balance";
fireUpdated();
updateBalanceIfNecessary(true);
completion = 1f;
statusText = "Done.";
fireUpdated();
fireBalanceUpdated();
}
}
@Override
public void shutdown() throws Exception {
clientStarted = false;
if (client != null)
client.stop();
}
public boolean isReady() {
return clientStarted;
}
public String getAcceptPaymentMethods() {
// TODO Add in escrow nodes ("escrow:<nodeid>,escrow:<nodeid>") here
return "upfront";
}
public Node[] getTrustedEscrowNodes() {
return new Node[0];
}
public String currencyUrl() {
return config.getCurrencyUrl();
}
public double getBidIncrement() {
return Math.pow(2,config.getBidIncrement());
}
public double getMinBid() {
return Math.pow(2,config.getMinBid());
}
public int getMinTopRate() {
return config.getMinTopRate();
}
public double getOpeningBalance() {
return Math.pow(2, config.getOpeningBalance());
}
public double getMaxBid(StreamVelocity sv) {
switch (sv) {
case LowestCost:
return Math.pow(2, config.getLowestCostMaxBid());
case MaxRate:
return Math.pow(2, config.getMaxRateMaxBid());
default:
throw new SeekInnerCalmException();
}
}
public double getBankBalance() throws CurrencyException {
return getBankBalance(false);
}
public double getBankBalance(boolean forceUpdate) throws CurrencyException {
updateBalanceIfNecessary(forceUpdate);
return cachedBankBalance;
}
public double getOnHandBalance() throws CurrencyException {
return client.getOnHandBalance();
}
public double depositToken(byte[] token, String narration) throws CurrencyException {
try {
CoinListMsg coins = CoinListMsg.parseFrom(token);
client.putCoins(coins);
double result = CoinList.totalValue(coins);
synchronized (this) {
cachedBankBalance += result;
}
fireAccountActivity(result, narration);
return result;
} catch (WangException e) {
throw new CurrencyException(e);
} catch (IOException e) {
throw new CurrencyException(e);
}
}
public byte[] withdrawToken(double value, String narration) throws CurrencyException {
try {
CoinListMsg coins = client.getCoins(value);
synchronized (this) {
cachedBankBalance -= CoinList.totalValue(coins);
}
fireAccountActivity(0 - value, narration);
return coins.toByteArray();
} catch (WangException e) {
throw new CurrencyException(e);
}
}
public void loggedIn() {
rbnb.getTaskService().runTask(new StartClientTask());
}
private void fireBalanceUpdated() {
rbnb.getEventService().fireWangBalanceChanged(cachedBankBalance+client.getOnHandBalance());
}
private void fireAccountActivity(final double creditValue, final String narration) {
rbnb.getExecutor().execute(new CatchingRunnable() {
public void doRun() throws Exception {
updateBalanceIfNecessary(false);
rbnb.getEventService().fireWangAccountActivity(creditValue, narration);
fireBalanceUpdated();
}
});
}
private void updateBalanceIfNecessary(boolean forceUpdate) {
synchronized (this) {
if (forceUpdate || now().after(nextCheckBalanceTime)) {
try {
cachedBankBalance = client.getAccurateBankBalance();
} catch (WangException e) {
log.error("Error updating bank balance", e);
}
nextCheckBalanceTime = timeInFuture(config.getBankBalanceCacheTime() * 1000);
}
}
}
}