Index: exampleparties/boulware/README.txt
===================================================================
--- exampleparties/boulware/README.txt	(revision 81)
+++ exampleparties/boulware/README.txt	(revision 81)
@@ -0,0 +1,10 @@
+You must first install geniusweb:
+
+pip install -r requirements.txt
+
+
+You build your party with
+
+python3 setup.py sdist
+
+result file in dist/randomparty-X.Y.Z.tar.gz
Index: exampleparties/boulware/boulware/Boulware.py
===================================================================
--- exampleparties/boulware/boulware/Boulware.py	(revision 81)
+++ exampleparties/boulware/boulware/Boulware.py	(revision 81)
@@ -0,0 +1,19 @@
+from timedependentparty.TimeDependentParty import TimeDependentParty
+from tudelft_utilities_logging.Reporter import Reporter
+
+class Boulware (TimeDependentParty):
+    """
+    A simple party that places random bids and accepts when it receives an offer
+    with sufficient utility.    
+    """
+    def __init__(self, reporter: Reporter=None):
+        super().__init__(reporter)
+
+    #Override
+    def getDescription(self) -> str:
+        return "Boulware: reluctant to concede. Parameters minPower (default 1) "\
+                + "and maxPower (default infinity) are used when voting"
+
+    #Override
+    def getE(self)->float:
+        return 0.2
Index: exampleparties/boulware/party.py
===================================================================
--- exampleparties/boulware/party.py	(revision 81)
+++ exampleparties/boulware/party.py	(revision 81)
@@ -0,0 +1,3 @@
+from boulware.Boulware import Boulware
+def party():
+    return Boulware
Index: exampleparties/boulware/requirements.txt
===================================================================
--- exampleparties/boulware/requirements.txt	(revision 81)
+++ exampleparties/boulware/requirements.txt	(revision 81)
@@ -0,0 +1,3 @@
+https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWebPython/export/80/exampleparties/timedependentparty/dist/timedependentparty-1.1.3.tar.gz
+mypy
+
Index: exampleparties/boulware/setup.py
===================================================================
--- exampleparties/boulware/setup.py	(revision 81)
+++ exampleparties/boulware/setup.py	(revision 81)
@@ -0,0 +1,13 @@
+from setuptools import setup, find_packages
+
+setup(
+    name='boulware',
+    version='1.1.3',    
+    description='A python3 party that places bids with monotonically decreasing utility',
+    url='https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWeb',
+    author='W.Pasman',
+    packages = find_packages(exclude=["test", "test.*","test.*.*"]),
+    package_data = { 'boulware': ['py.typed']    },
+    install_requires=[ "timedependentparty@https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWebPython/export/80/exampleparties/timedependentparty/dist/timedependentparty-1.1.3.tar.gz"],
+    py_modules=['party']
+)
Index: exampleparties/conceder/README.txt
===================================================================
--- exampleparties/conceder/README.txt	(revision 81)
+++ exampleparties/conceder/README.txt	(revision 81)
@@ -0,0 +1,10 @@
+You must first install geniusweb:
+
+pip install -r requirements.txt
+
+
+You build your party with
+
+python3 setup.py sdist
+
+result file in dist/randomparty-X.Y.Z.tar.gz
Index: exampleparties/conceder/conceder/Conceder.py
===================================================================
--- exampleparties/conceder/conceder/Conceder.py	(revision 81)
+++ exampleparties/conceder/conceder/Conceder.py	(revision 81)
@@ -0,0 +1,18 @@
+from timedependentparty.TimeDependentParty import TimeDependentParty
+from tudelft_utilities_logging.Reporter import Reporter
+
+class Conceder (TimeDependentParty):
+    """
+    A simple party that places random bids and accepts when it receives an offer
+    with sufficient utility.    
+    """
+    def __init__(self, reporter: Reporter=None):
+        super().__init__(reporter)
+
+    #Override
+    def getDescription(self) -> str:
+        return "Conceder: going to the reservation value very quickly. "\
+                + "Parameters minPower (default 1) and maxPower (default infinity) are used when voting"
+    #Override
+    def getE(self)->float:
+        return 2.0
Index: exampleparties/conceder/party.py
===================================================================
--- exampleparties/conceder/party.py	(revision 81)
+++ exampleparties/conceder/party.py	(revision 81)
@@ -0,0 +1,4 @@
+from conceder.Conceder import Conceder
+
+def party():
+    return Conceder
Index: exampleparties/conceder/requirements.txt
===================================================================
--- exampleparties/conceder/requirements.txt	(revision 81)
+++ exampleparties/conceder/requirements.txt	(revision 81)
@@ -0,0 +1,3 @@
+https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWebPython/export/80/exampleparties/timedependentparty/dist/timedependentparty-1.1.3.tar.gz
+mypy
+
Index: exampleparties/conceder/setup.py
===================================================================
--- exampleparties/conceder/setup.py	(revision 81)
+++ exampleparties/conceder/setup.py	(revision 81)
@@ -0,0 +1,13 @@
+from setuptools import setup, find_packages
+
+setup(
+    name='conceder',
+    version='1.1.3',    
+    description='A python3 party that places bids with monotonically decreasing utility',
+    url='https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWeb',
+    author='W.Pasman',
+    packages = find_packages(exclude=["test", "test.*","test.*.*"]),
+    package_data = { 'conceder': ['py.typed']    },
+    install_requires=[ "timedependentparty@https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWebPython/export/80/exampleparties/timedependentparty/dist/timedependentparty-1.1.3.tar.gz"],
+    py_modules=['party']
+)
Index: exampleparties/hardliner/README.txt
===================================================================
--- exampleparties/hardliner/README.txt	(revision 81)
+++ exampleparties/hardliner/README.txt	(revision 81)
@@ -0,0 +1,10 @@
+You must first install geniusweb:
+
+pip install -r requirements.txt
+
+
+You build your party with
+
+python3 setup.py sdist
+
+result file in dist/randomparty-X.Y.Z.tar.gz
Index: exampleparties/hardliner/hardliner/Hardliner.py
===================================================================
--- exampleparties/hardliner/hardliner/Hardliner.py	(revision 81)
+++ exampleparties/hardliner/hardliner/Hardliner.py	(revision 81)
@@ -0,0 +1,19 @@
+from timedependentparty.TimeDependentParty import TimeDependentParty
+from tudelft_utilities_logging.Reporter import Reporter
+
+class Hardliner (TimeDependentParty):
+    """
+    A simple party that places random bids and accepts when it receives an offer
+    with sufficient utility.    
+    """
+    def __init__(self, reporter: Reporter=None):
+        super().__init__(reporter)
+
+    #Override
+    def getDescription(self) -> str:
+        return "Hardliner: does not concede. "\
+                + "Parameters minPower (default 1) and maxPower (default infinity) are used when voting"
+
+    #Override
+    def getE(self)->float:
+        return 0.0
Index: exampleparties/hardliner/party.py
===================================================================
--- exampleparties/hardliner/party.py	(revision 81)
+++ exampleparties/hardliner/party.py	(revision 81)
@@ -0,0 +1,4 @@
+from hardliner.Hardliner import Hardliner
+
+def party():
+    return Hardliner
Index: exampleparties/hardliner/requirements.txt
===================================================================
--- exampleparties/hardliner/requirements.txt	(revision 81)
+++ exampleparties/hardliner/requirements.txt	(revision 81)
@@ -0,0 +1,3 @@
+https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWebPython/export/80/exampleparties/timedependentparty/dist/timedependentparty-1.1.3.tar.gz
+mypy
+
Index: exampleparties/hardliner/setup.py
===================================================================
--- exampleparties/hardliner/setup.py	(revision 81)
+++ exampleparties/hardliner/setup.py	(revision 81)
@@ -0,0 +1,13 @@
+from setuptools import setup, find_packages
+
+setup(
+    name='hardliner',
+    version='1.1.3',    
+    description='A python3 party that places bids with monotonically decreasing utility',
+    url='https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWeb',
+    author='W.Pasman',
+    packages = find_packages(exclude=["test", "test.*","test.*.*"]),
+    package_data = { 'hardliner': ['py.typed']    },
+    install_requires=[ "timedependentparty@https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWebPython/export/80/exampleparties/timedependentparty/dist/timedependentparty-1.1.3.tar.gz"],
+    py_modules=['party']
+)
Index: exampleparties/linear/README.txt
===================================================================
--- exampleparties/linear/README.txt	(revision 81)
+++ exampleparties/linear/README.txt	(revision 81)
@@ -0,0 +1,10 @@
+You must first install geniusweb:
+
+pip install -r requirements.txt
+
+
+You build your party with
+
+python3 setup.py sdist
+
+result file in dist/randomparty-X.Y.Z.tar.gz
Index: exampleparties/linear/linear/Linear.py
===================================================================
--- exampleparties/linear/linear/Linear.py	(revision 81)
+++ exampleparties/linear/linear/Linear.py	(revision 81)
@@ -0,0 +1,18 @@
+from timedependentparty.TimeDependentParty import TimeDependentParty
+from tudelft_utilities_logging.Reporter import Reporter
+
+class Linear (TimeDependentParty):
+    """
+    A simple party that places random bids and accepts when it receives an offer
+    with sufficient utility.    
+    """
+    def __init__(self, reporter: Reporter=None):
+        super().__init__(reporter)
+
+    #Override
+    def getDescription(self) -> str:
+        return "Linear: concedes linearly with time. "\
+                + "Parameters minPower (default 1) and maxPower (default infinity) are used when voting"
+    #Override
+    def getE(self)->float:
+        return 1.0
Index: exampleparties/linear/party.py
===================================================================
--- exampleparties/linear/party.py	(revision 81)
+++ exampleparties/linear/party.py	(revision 81)
@@ -0,0 +1,4 @@
+from linear.Linear import Linear
+
+def party():
+    return Linear
Index: exampleparties/linear/requirements.txt
===================================================================
--- exampleparties/linear/requirements.txt	(revision 81)
+++ exampleparties/linear/requirements.txt	(revision 81)
@@ -0,0 +1,3 @@
+https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWebPython/export/80/exampleparties/timedependentparty/dist/timedependentparty-1.1.3.tar.gz
+mypy
+
Index: exampleparties/linear/setup.py
===================================================================
--- exampleparties/linear/setup.py	(revision 81)
+++ exampleparties/linear/setup.py	(revision 81)
@@ -0,0 +1,13 @@
+from setuptools import setup, find_packages
+
+setup(
+    name='linear',
+    version='1.1.3',    
+    description='A python3 party that places bids with monotonically decreasing utility',
+    url='https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWeb',
+    author='W.Pasman',
+    packages = find_packages(exclude=["test", "test.*","test.*.*"]),
+    package_data = { 'linear': ['py.typed']    },
+    install_requires=[ "timedependentparty@https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWebPython/export/80/exampleparties/timedependentparty/dist/timedependentparty-1.1.3.tar.gz"],
+    py_modules=['party']
+)
Index: exampleparties/randomparty/README.txt
===================================================================
--- exampleparties/randomparty/README.txt	(revision 80)
+++ exampleparties/randomparty/README.txt	(revision 81)
@@ -1,3 +1,3 @@
-You must first install geniusweb:
+You must first install the required libraries:
 
 pip install -r requirements.txt
Index: exampleparties/randomparty/requirements.txt
===================================================================
--- exampleparties/randomparty/requirements.txt	(revision 80)
+++ exampleparties/randomparty/requirements.txt	(revision 81)
@@ -1,3 +1,3 @@
-https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWebPython/export/70/geniuswebcore/dist/geniusweb-1.1.2.tar.gz
+https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWebPython/export/79/geniuswebcore/dist/geniusweb-1.1.3.tar.gz
 mypy
 
Index: exampleparties/randomparty/setup.py
===================================================================
--- exampleparties/randomparty/setup.py	(revision 80)
+++ exampleparties/randomparty/setup.py	(revision 81)
@@ -3,10 +3,10 @@
 setup(
     name='randomparty',
-    version='1.1.2',    
+    version='1.1.3',    
     description='A python3 party that places random bids with sufficient utility',
     url='https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWeb',
     author='W.Pasman',
     packages=['randomparty'],
-    install_requires=[ "geniusweb@https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWebPython/export/70/geniuswebcore/dist/geniusweb-1.1.2.tar.gz"],
+    install_requires=[ "geniusweb@https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWebPython/export/79/geniuswebcore/dist/geniusweb-1.1.3.tar.gz"],
     py_modules=['party']
 )
Index: exampleparties/stupidparty/README.txt
===================================================================
--- exampleparties/stupidparty/README.txt	(revision 80)
+++ exampleparties/stupidparty/README.txt	(revision 81)
@@ -1,3 +1,3 @@
-You must first install geniusweb:
+You must first install the required libraries:
 
 pip install -r requirements.txt
Index: exampleparties/stupidparty/requirements.txt
===================================================================
--- exampleparties/stupidparty/requirements.txt	(revision 80)
+++ exampleparties/stupidparty/requirements.txt	(revision 81)
@@ -1,3 +1,3 @@
-https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWebPython/export/70/geniuswebcore/dist/geniusweb-1.1.2.tar.gz
+https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWebPython/export/79/geniuswebcore/dist/geniusweb-1.1.3.tar.gz
 mypy
 
Index: exampleparties/stupidparty/setup.py
===================================================================
--- exampleparties/stupidparty/setup.py	(revision 80)
+++ exampleparties/stupidparty/setup.py	(revision 81)
@@ -3,10 +3,10 @@
 setup(
     name='stupidparty',
-    version='1.1.2',    
+    version='1.1.3',    
     description='A example python3 party module',
     url='https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWeb',
     author='W.Pasman',
     packages=['stupidparty'],
-    install_requires=[ "geniusweb@https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWebPython/export/70/geniuswebcore/dist/geniusweb-1.1.2.tar.gz"],
+    install_requires=[ "geniusweb@https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWebPython/export/79/geniuswebcore/dist/geniusweb-1.1.3.tar.gz"],
     py_modules=['party'],
 
Index: exampleparties/timedependentparty/README.txt
===================================================================
--- exampleparties/timedependentparty/README.txt	(revision 81)
+++ exampleparties/timedependentparty/README.txt	(revision 81)
@@ -0,0 +1,10 @@
+You must first install the required libraries:
+
+pip install -r requirements.txt
+
+
+You build your party with
+
+python3 setup.py sdist
+
+result file in dist/timedependentparty-X.Y.Z.tar.gz
Index: exampleparties/timedependentparty/party.py
===================================================================
--- exampleparties/timedependentparty/party.py	(revision 81)
+++ exampleparties/timedependentparty/party.py	(revision 81)
@@ -0,0 +1,4 @@
+from timedependentparty.TimeDependentParty import TimeDependentParty
+
+def party():
+    return TimeDependentParty
Index: exampleparties/timedependentparty/requirements.txt
===================================================================
--- exampleparties/timedependentparty/requirements.txt	(revision 81)
+++ exampleparties/timedependentparty/requirements.txt	(revision 81)
@@ -0,0 +1,3 @@
+https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWebPython/export/79/geniuswebcore/dist/geniusweb-1.1.3.tar.gz
+mypy
+
Index: exampleparties/timedependentparty/setup.py
===================================================================
--- exampleparties/timedependentparty/setup.py	(revision 81)
+++ exampleparties/timedependentparty/setup.py	(revision 81)
@@ -0,0 +1,13 @@
+from setuptools import setup, find_packages
+
+setup(
+    name='timedependentparty',
+    version='1.1.3',    
+    description='A python3 party that places bids with monotonically decreasing utility',
+    url='https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWeb',
+    author='W.Pasman',
+    packages = find_packages(exclude=["test", "test.*","test.*.*"]),
+    package_data = { 'timedependentparty': ['py.typed']    },
+    install_requires=[ "geniusweb@https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWebPython/export/79/geniuswebcore/dist/geniusweb-1.1.3.tar.gz"],
+    py_modules=['party']
+)
Index: exampleparties/timedependentparty/test/ExtendedUtilSpaceTest.py
===================================================================
--- exampleparties/timedependentparty/test/ExtendedUtilSpaceTest.py	(revision 81)
+++ exampleparties/timedependentparty/test/ExtendedUtilSpaceTest.py	(revision 81)
@@ -0,0 +1,48 @@
+import unittest
+from pyson.ObjectMapper import ObjectMapper
+from decimal import Decimal
+from geniusweb.profile.utilityspace.LinearAdditive import LinearAdditive
+import json
+from pathlib import Path
+from geniusweb.profile.utilityspace.LinearAdditiveUtilitySpace import LinearAdditiveUtilitySpace
+from timedependentparty.ExtendedUtilSpace import ExtendedUtilSpace
+
+class ExtendedUtilSpaceTest(unittest.TestCase):
+	jackson = ObjectMapper()
+	SMALL = Decimal("0.0001")
+
+
+	parameters=[
+		["test/resources/jobs/jobs1.json", Decimal(0.02) ],
+		[ "test/resources/7issues/7issues1.json", Decimal(0.0055) ],
+		["test/resources/9issues/9issues1.json", Decimal(0.0013) ] 
+	]
+	
+	def testAll(self):
+		''' 
+		runs all the tests. Python has no standard 'parameterized' mechanism
+		so we have to do it ourselves.
+		'''
+		for [filename, expectedtolerance] in self.parameters:
+			self.filename=filename
+			self.expectedTolerance= expectedtolerance
+			with self.subTest("testsmokeTest"+self.filename):
+				self.before()
+				self.smoke()
+			with self.subTest("testTolerance"+self.filename):
+				self.before()
+				self.Tolerance()
+
+
+
+	def before(self) :
+		file = Path(self.filename).read_text("utf-8")
+		profile=self.jackson.parse(json.loads(file), LinearAdditiveUtilitySpace)
+		self.space=ExtendedUtilSpace(profile)
+		print("self.space="+str(self.space))
+
+	def smoke(self): 
+		pass
+	
+	def Tolerance(self):
+		self.assertTrue(abs(self.space._computeTolerance()-self.expectedTolerance) < self.SMALL)
Index: exampleparties/timedependentparty/test/TestConnection.py
===================================================================
--- exampleparties/timedependentparty/test/TestConnection.py	(revision 81)
+++ exampleparties/timedependentparty/test/TestConnection.py	(revision 81)
@@ -0,0 +1,40 @@
+from typing import List, Optional
+from tudelft.utilities.listener.DefaultListenable import DefaultListenable
+from geniusweb.actions.Action import Action
+from geniusweb.inform.Inform import Inform
+from geniusweb.connection.ConnectionEnd import ConnectionEnd
+from geniusweb.references.Reference import Reference
+from uri.uri import URI
+
+class TestConnection(DefaultListenable[Inform], ConnectionEnd[Inform, Action]):
+	'''
+	A "real" connection object, because the party is going to subscribe etc, and
+	without a real connection we would have to do a lot of mocks that would make
+	the test very hard to read.
+	'''
+	
+	def __init__(self):
+		super().__init__()
+		self.actions: List[Action]  = []
+
+	#Override
+	def send(self , action:Action ):
+		self.actions.append(action)
+
+	#Override
+	def getReference(self)-> Reference :
+		return None #type:ignore
+
+	#Override
+	def getRemoteURI(self) -> URI :
+		return None #type:ignore
+
+	def  close(self):
+		pass
+	 
+	#Override
+	def getError(self)->Optional[Exception] :
+		return None
+
+	def getActions(self) -> List[Action] :
+		return self.actions
Index: exampleparties/timedependentparty/test/TimeDependentPartyMOPACTest.java
===================================================================
--- exampleparties/timedependentparty/test/TimeDependentPartyMOPACTest.java	(revision 81)
+++ exampleparties/timedependentparty/test/TimeDependentPartyMOPACTest.java	(revision 81)
@@ -0,0 +1,196 @@
+package geniusweb.exampleparties.timedependentparty;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import geniusweb.actions.Action;
+import geniusweb.actions.Offer;
+import geniusweb.actions.PartyId;
+import geniusweb.actions.Vote;
+import geniusweb.actions.Votes;
+import geniusweb.inform.OptIn;
+import geniusweb.inform.Settings;
+import geniusweb.inform.Voting;
+import geniusweb.inform.YourTurn;
+import geniusweb.issuevalue.Bid;
+import geniusweb.issuevalue.DiscreteValue;
+import geniusweb.issuevalue.NumberValue;
+import geniusweb.issuevalue.Value;
+import geniusweb.profile.Profile;
+import geniusweb.profile.utilityspace.LinearAdditiveUtilitySpace;
+import geniusweb.progress.ProgressRounds;
+import geniusweb.references.Parameters;
+import geniusweb.references.ProfileRef;
+import geniusweb.references.ProtocolRef;
+
+public class TimeDependentPartyMOPACTest {
+
+	private static final PartyId me = new PartyId("me");
+	private static final PartyId otherparty = new PartyId("other");
+	private static final String MOPAC = "MOPAC";
+	private static final String PROFILE = "src/test/resources/testprofile.json";
+	private final static ObjectMapper jackson = new ObjectMapper();
+
+	private TimeDependentParty party;
+	private TestConnection connection = new TestConnection();
+	private ProtocolRef protocol = new ProtocolRef("MOPAC");
+	private ProgressRounds progress = mock(ProgressRounds.class);
+	private Parameters parameters = new Parameters();
+	private Settings settings;
+	private LinearAdditiveUtilitySpace profile;
+	private Map<PartyId, Integer> powers = new HashMap<>();
+	private Bid bestBid;
+
+	@Before
+	public void before() throws JsonParseException, JsonMappingException,
+			IOException, URISyntaxException {
+		powers.put(me, 1);
+		powers.put(otherparty, 1);
+
+		party = new TimeDependentParty() {
+
+			@Override
+			public String getDescription() {
+				return "test";
+			}
+
+			@Override
+			public double getE() {
+				return 2; // conceder-like
+			}
+		};
+		settings = new Settings(me, new ProfileRef(new URI("file:" + PROFILE)),
+				protocol, progress, parameters);
+
+		String serialized = new String(Files.readAllBytes(Paths.get(PROFILE)),
+				StandardCharsets.UTF_8);
+		profile = (LinearAdditiveUtilitySpace) jackson.readValue(serialized,
+				Profile.class);
+
+		// hard coded best bid for this domain
+		Map<String, Value> vals = new HashMap<>();
+		vals.put("issue2", new NumberValue(new BigDecimal("18")));
+		vals.put("issue1", new DiscreteValue("issue1value2"));
+		bestBid = new Bid(vals);
+
+	}
+
+	@Test
+	public void testPartyFirstOffers() {
+		party.connect(connection);
+		party.notifyChange(settings);
+
+		party.notifyChange(new YourTurn());
+		assertEquals(1, connection.getActions().size());
+		assertTrue(connection.getActions().get(0) instanceof Offer);
+	}
+
+	@Test
+	public void checkPartyFirstBid() {
+		party.connect(connection);
+		party.notifyChange(settings);
+
+		// let party make an offer and go to the voting phase
+		party.notifyChange(new YourTurn());
+		Offer offer = (Offer) connection.getActions().get(0);
+
+		// it's either the best or one-but-best bid,
+		// because we set up the tolerance like that.
+		Bid bid = offer.getBid();
+		assertEquals("issue1value2",
+				((DiscreteValue) bid.getValue("issue1")).getValue());
+		assertTrue(((NumberValue) bid.getValue("issue2")).getValue()
+				.compareTo(new BigDecimal("17")) >= 0);
+	}
+
+	@Test
+	public void testPartyVotesForBest() {
+		party.connect(connection);
+		party.notifyChange(settings);
+
+		// let party make an offer and go to the voting phase
+		party.notifyChange(new YourTurn());
+		assertEquals(1, connection.getActions().size());
+
+		// send bestBid bid as the voting option
+		// this is better than party's firstBid
+
+		party.notifyChange(new Voting(
+				Arrays.asList(new Offer(otherparty, bestBid)), powers));
+		assertEquals(2, connection.getActions().size());
+
+		Action lastAction = connection.getActions().get(1);
+		assertTrue(lastAction instanceof Votes);
+		assertEquals(
+				new Votes(me,
+						Collections.singleton(
+								new Vote(me, bestBid, 1, Integer.MAX_VALUE))),
+				lastAction);
+	}
+
+	@Test
+	public void testPartyOptsInBest() {
+		party.connect(connection);
+		party.notifyChange(settings);
+
+		// let party make an offer and go to the voting phase
+		party.notifyChange(new YourTurn());
+		assertEquals(1, connection.getActions().size());
+
+		// send bestBid bid as the voting option
+		// this is better than party's firstBid
+
+		party.notifyChange(new Voting(Arrays.asList(new Offer(me, bestBid),
+				new Offer(otherparty, bestBid)), powers));
+		assertEquals(2, connection.getActions().size());
+
+		// we already checked the party votes for bestBid
+		Votes votes = (Votes) connection.getActions().get(1);
+
+		party.notifyChange(new OptIn(Arrays.asList(votes)));
+		assertEquals(3, connection.getActions().size());
+
+		Action lastAction = connection.getActions().get(2);
+		assertTrue(lastAction instanceof Votes);
+		assertTrue(((Votes) lastAction).isExtending(votes));
+	}
+
+	@Test
+	public void testGetCapabilities() {
+		assertTrue(party.getCapabilities().getBehaviours().contains(MOPAC));
+	}
+
+//	private Bid findBestBid() {
+//		BigDecimal bestvalue = BigDecimal.ZERO;
+//		Bid best = null;
+//		for (Bid bid : new AllBidsList(profile.getDomain())) {
+//			BigDecimal util = profile.getUtility(bid);
+//			if (util.compareTo(bestvalue) > 0) {
+//				best = bid;
+//				bestvalue = util;
+//			}
+//		}
+//		return best;
+//	}
+
+}
Index: exampleparties/timedependentparty/test/TimeDependentPartyTest.py
===================================================================
--- exampleparties/timedependentparty/test/TimeDependentPartyTest.py	(revision 81)
+++ exampleparties/timedependentparty/test/TimeDependentPartyTest.py	(revision 81)
@@ -0,0 +1,196 @@
+import unittest
+from unittest.mock import Mock
+from geniusweb.actions.PartyId import PartyId
+from pyson.ObjectMapper import ObjectMapper
+from geniusweb.references.ProtocolRef import ProtocolRef
+from geniusweb.progress.ProgressRounds import ProgressRounds
+from geniusweb.references.Parameters import Parameters
+from test.TestConnection import TestConnection
+from timedependentparty.TimeDependentParty import TimeDependentParty
+from uri.uri import URI
+from geniusweb.references.ProfileRef import ProfileRef
+from geniusweb.inform.Settings import Settings
+from pathlib import Path
+from geniusweb.profile.utilityspace.LinearAdditiveUtilitySpace import LinearAdditiveUtilitySpace
+import json
+from geniusweb.inform.ActionDone import ActionDone
+from geniusweb.actions.EndNegotiation import EndNegotiation
+from geniusweb.inform.YourTurn import YourTurn
+from geniusweb.actions.Offer import Offer
+from geniusweb.actions.Accept import Accept
+from tudelft_utilities_logging.Reporter import Reporter
+from geniusweb.inform.Agreements import Agreements
+from geniusweb.inform.Finished import Finished
+import logging
+from decimal import Decimal
+from geniusweb.bidspace.AllBidsList import AllBidsList
+import time
+from geniusweb.issuevalue.Bid import Bid
+
+class TimeDependentPartyTest (unittest.TestCase):
+	otherparty = PartyId("other")
+	SAOP="SAOP"
+	PROFILE = "test/resources/testprofile.json"
+	jackson = ObjectMapper()
+
+	protocol = ProtocolRef(URI("SAOP"))
+
+	def setUp(self) :
+		self.connection = TestConnection()
+		self.progress = Mock(ProgressRounds)
+		self.progress.get=Mock(return_value=0)
+		self.parameters =  Parameters()
+		class MyTimeDependentParty(TimeDependentParty):
+			#Override
+			def getDescription(self) ->str:
+				return "test"
+
+			#Override
+			def getE(self) ->float:
+				return 2 # conceder-like
+		
+		self.party = MyTimeDependentParty()
+
+		self.settings = Settings(PartyId("party1"),
+				ProfileRef(URI("file:" + self.PROFILE)), self.protocol, self.progress,
+				self.parameters)
+
+		serialized = Path(self.PROFILE).read_text("utf-8")
+		self.profile = self.jackson.parse(json.loads(serialized), LinearAdditiveUtilitySpace)
+
+
+	def testsmokeTest(self):
+		pass
+
+	def  testgetDescriptionTest(self):
+		self.assertNotEquals(None, self.party.getDescription())
+
+	def testgetCapabilitiesTest(self):
+		capabilities = self.party.getCapabilities()
+		self.assertFalse(capabilities.getBehaviours()==set(),
+				"party does not define protocols")
+
+	def testInformConnection(self):
+		self.party.connect(self.connection)
+		# Party should not start acting just after an inform
+		self.assertEqual(0, len(self.connection.getActions()))
+
+	def testInformSettings(self):
+		self.party.connect(self.connection)
+		self.connection.notifyListeners(self.settings)
+		self.assertEqual(0, len(self.connection.getActions()))
+
+	def testInformAndConnection(self):
+		self.party.connect(self.connection)
+		self.party.notifyChange(self.settings)
+		self.assertEqual(0, len(self.connection.getActions()))
+
+	def testOtherWalksAway(self):
+		self.party.connect(self.connection)
+		self.party.notifyChange(self.settings)
+
+		self.party.notifyChange(ActionDone(EndNegotiation(self.otherparty)))
+
+		# party should not act at this point
+		self.assertEqual(0, len(self.connection.getActions()))
+
+	def testPartyHasFirstTurn(self):
+		self.party.connect(self.connection)
+		self.party.notifyChange(self.settings)
+
+		self.party.notifyChange(YourTurn())
+		self.assertEqual(1, len(self.connection.getActions()))
+		self.assertTrue(isinstance(self.connection.getActions()[0], Offer))
+
+	def testPartyAccepts(self):
+		self.party.connect(self.connection)
+		self.party.notifyChange(self.settings)
+
+		bid = self._findBestBid()
+		self.party.notifyChange(ActionDone(Offer(self.otherparty, bid)))
+		self.party.notifyChange(YourTurn())
+		self.assertEqual(1, len(self.connection.getActions()))
+		self.assertTrue(isinstance(self.connection.getActions()[0], Accept))
+
+	def testPartyLogsFinal(self):
+		# this log output is optional, this is to show how to check log
+		reporter = Mock(Reporter)
+		class MyTimeDependentParty(TimeDependentParty):
+			def __init__(self, reporter:Reporter):
+				super().__init__(reporter)
+			#Override
+			def getDescription(self) ->str:
+				return "test"
+
+			#Override
+			def getE(self) ->float:
+				return 2 # conceder-like
+		
+
+		pty = MyTimeDependentParty(reporter) 
+		pty.connect(self.connection)
+		pty.notifyChange(self.settings)
+		agreements = Mock(Agreements)
+		agreements.__str__=Mock(return_value="agree");
+		pty.notifyChange(Finished(agreements))
+		unittest.mock._Call
+
+		reporter_call_strings = [ a.args[1] for a in reporter.log.call_args_list]
+		self.assertTrue( "Final ourcome:Finished[agree]" in reporter_call_strings)
+
+	#Test
+	def testPartysUpdatesProgress(self):
+		self.party.connect(self.connection)
+		self.party.notifyChange(self.settings)
+
+		self.party.notifyChange(YourTurn())
+		self.assertTrue( len(self.progress.advance.call_args_list) >0 )
+
+	def testGetCapabilities(self):
+		saop=self.SAOP # PYTHON BUG? Why can't I use this directly in assert??
+		self.assertTrue(saop in self.party.getCapabilities().getBehaviours())
+
+	def testUtilityTarget(self):
+		tdp = TimeDependentParty()
+		N02 = Decimal("0.2")
+		N043 =Decimal("0.42521212")
+		goal = tdp._getUtilityGoal(0.1, 1.2, N02, N043)
+		self.assertTrue(goal> N02)
+		self.assertTrue(goal<N043)
+
+	def _findBestBid(self)->Bid:
+		bestvalue = Decimal(0)
+		best = None
+		for bid in AllBidsList(self.profile.getDomain()):
+			util = self.profile.getUtility(bid)
+			if util>bestvalue:
+				best = bid
+				bestvalue = util
+		return best #type:ignore
+
+	def testPartyAcceptsWithDelay(self):
+		'''
+		Check that delay indeed waits
+		
+		@throws URISyntaxException
+		'''
+		self.party.connect(self.connection)
+
+		settingsdelay = Settings(PartyId("party1"),
+				 ProfileRef(URI("file:" + self.PROFILE)), self.protocol, self.progress,
+				self.parameters.With("delay", 2))
+
+		self.party.notifyChange(settingsdelay);
+
+		bid = self._findBestBid()
+		self.party.notifyChange(ActionDone(Offer(self.otherparty, bid)))
+		start = round(time.time()*1000)
+		self.party.notifyChange( YourTurn())
+		end = round(time.time()*1000)
+		self.assertEqual(1, len(self.connection.getActions()))
+		self.assertTrue(isinstance(self.connection.getActions()[0] , Accept))
+
+		dt = end - start
+		self.assertTrue(dt >= 1000)
+		self.assertTrue(dt <= 3000)
+
Index: exampleparties/timedependentparty/test/resources/7issues/7issues1.json
===================================================================
--- exampleparties/timedependentparty/test/resources/7issues/7issues1.json	(revision 81)
+++ exampleparties/timedependentparty/test/resources/7issues/7issues1.json	(revision 81)
@@ -0,0 +1,105 @@
+{
+	"LinearAdditiveUtilitySpace": {
+		"name": "7issues1",
+		"issueUtilities": {
+			"i1": {
+				"NumberValueSetUtilities": {
+					"lowValue": 0,
+					"lowUtility": 0.11,
+					"highValue": 9,
+					"highUtility": 0.99
+
+				}
+			},
+			"i2": {
+				"NumberValueSetUtilities": {
+					"lowValue": 0,
+					"lowUtility": 0.12,
+					"highValue": 9,
+					"highUtility": 0.97
+
+				}
+			},
+			"i3": {
+				"NumberValueSetUtilities": {
+					"lowValue": 0,
+					"lowUtility": 0.13,
+					"highValue": 9,
+					"highUtility": 0.96
+
+				}
+			},
+			"i4": {
+				"NumberValueSetUtilities": {
+					"lowValue": 0,
+					"lowUtility": 0.14,
+					"highValue": 9,
+					"highUtility": 0.94
+
+				}
+			},
+			"i5": {
+				"NumberValueSetUtilities": {
+					"lowValue": 0,
+					"lowUtility": 0.15,
+					"highValue": 9,
+					"highUtility": 0.93
+
+				}
+			},
+			"i6": {
+				"NumberValueSetUtilities": {
+					"lowValue": 0,
+					"lowUtility": 0.16,
+					"highValue": 9,
+					"highUtility": 0.92
+
+				}
+			},
+			"i7": {
+				"NumberValueSetUtilities": {
+					"lowValue": 0,
+					"lowUtility": 0.16,
+					"highValue": 9,
+					"highUtility": 0.91
+
+				}
+			}
+		},
+		"issueWeights": {
+			"i1": 0.07531677352656704,
+			"i2": 0.18148468268008364,
+			"i3": 0.13035193969082373,
+			"i4": 0.24649985443378972,
+			"i5": 0.16931694966873587,
+			"i6": 0.131353,
+			"i7": 0.0656768
+		},
+		"domain": {
+			"name": "7issues",
+			"issuesValues": {
+				"i1": {
+					"range": {"low":0,"high":9,"step":1}
+				},
+				"i2": {
+					"range": {"low":0,"high":9,"step":1}
+				},
+				"i3": {
+					"range": {"low":0,"high":9,"step":1}
+				},
+				"i4": {
+					"range": {"low":0,"high":9,"step":1}
+				},
+				"i5": {
+					"range": {"low":0,"high":9,"step":1}
+				},
+				"i6": {
+					"range": {"low":0,"high":9,"step":1}
+				},
+				"i7": {
+					"range": {"low":0,"high":9,"step":1}
+				}
+			}
+		}
+	}
+}
Index: exampleparties/timedependentparty/test/resources/9issues/9issues1.json
===================================================================
--- exampleparties/timedependentparty/test/resources/9issues/9issues1.json	(revision 81)
+++ exampleparties/timedependentparty/test/resources/9issues/9issues1.json	(revision 81)
@@ -0,0 +1,134 @@
+{
+	"LinearAdditiveUtilitySpace": {
+		"name": "9issues1",
+		"issueUtilities": {
+			"i1": {
+				"NumberValueSetUtilities": {
+					"lowValue": 0,
+					"lowUtility": 0.11,
+					"highValue": 9,
+					"highUtility": 0.99
+
+				}
+			},
+			"i2": {
+				"NumberValueSetUtilities": {
+					"lowValue": 0,
+					"lowUtility": 0.12,
+					"highValue": 9,
+					"highUtility": 0.97
+
+				}
+			},
+			"i3": {
+				"NumberValueSetUtilities": {
+					"lowValue": 0,
+					"lowUtility": 0.13,
+					"highValue": 9,
+					"highUtility": 0.96
+
+				}
+			},
+			"i4": {
+				"NumberValueSetUtilities": {
+					"lowValue": 0,
+					"lowUtility": 0.14,
+					"highValue": 9,
+					"highUtility": 0.94
+
+				}
+			},
+			"i5": {
+				"NumberValueSetUtilities": {
+					"lowValue": 0,
+					"lowUtility": 0.15,
+					"highValue": 9,
+					"highUtility": 0.93
+
+				}
+			},
+			"i6": {
+				"NumberValueSetUtilities": {
+					"lowValue": 0,
+					"lowUtility": 0.16,
+					"highValue": 9,
+					"highUtility": 0.92
+
+				}
+			},
+			"i7": {
+				"NumberValueSetUtilities": {
+					"lowValue": 0,
+					"lowUtility": 0.16,
+					"highValue": 9,
+					"highUtility": 0.91
+
+				}
+			},
+			"i8": {
+				"NumberValueSetUtilities": {
+					"lowValue": 0,
+					"lowUtility": 0.7664,
+					"highValue": 9,
+					"highUtility": 0.183
+
+				}
+			},
+
+			"i9": {
+				"NumberValueSetUtilities": {
+					"lowValue": 0,
+					"lowUtility": 0.191,
+					"highValue": 9,
+					"highUtility": 0.891
+
+				}
+			}
+
+		},
+		"issueWeights": {
+			"i1": 0.07531677352656,
+			"i2": 0.18148468268008,
+			"i3": 0.12035193969082,
+			"i4": 0.21649985443378,
+			"i5": 0.15621694966876,
+			"i6": 0.111253,
+			"i7": 0.0656768,
+			"i8": 0.0201,
+			"i9": 0.0531
+		},
+		"domain": {
+			"name": "9issues",
+			"issuesValues": {
+				"i1": {
+					"range": {"low":0,"high":9,"step":1}
+				},
+				"i2": {
+					"range": {"low":0,"high":9,"step":1}
+				},
+				"i3": {
+					"range": {"low":0,"high":9,"step":1}
+				},
+				"i4": {
+					"range": {"low":0,"high":9,"step":1}
+				},
+				"i5": {
+					"range": {"low":0,"high":9,"step":1}
+				},
+				"i6": {
+					"range": {"low":0,"high":9,"step":1}
+				},
+				"i7": {
+					"range": {"low":0,"high":9,"step":1}
+				},
+				"i8": {
+					"range": {"low":0,"high":9,"step":1}
+				},
+				"i9": {
+					"range": {"low":0,"high":9,"step":1}
+				}
+
+			}
+		}
+	}
+}
Index: exampleparties/timedependentparty/test/resources/jobs/jobs1.json
===================================================================
--- exampleparties/timedependentparty/test/resources/jobs/jobs1.json	(revision 81)
+++ exampleparties/timedependentparty/test/resources/jobs/jobs1.json	(revision 81)
@@ -0,0 +1,116 @@
+{
+	"LinearAdditiveUtilitySpace": {
+		"issueUtilities": {
+			"salary": {
+				"DiscreteValueSetUtilities": {
+					"valueUtilities": {
+						"2000": 0,
+						"2500": 0.25,
+						"3000": 0.3,
+						"3500": 0.75,
+						"4000": 1.0
+					}
+				}
+			},
+			"fte": {
+				"DiscreteValueSetUtilities": {
+					"valueUtilities": {
+						"0.6": 0.25,
+						"0.8": 0.5,
+						"1.0": 0.75
+					}
+				}
+			},
+			"work from home": {
+				"DiscreteValueSetUtilities": {
+					"valueUtilities": {
+						"0": 0.333333333,
+						"1": 0.5,
+						"2": 0.666666666666
+					}
+				}
+			},
+			"lease car": {
+				"DiscreteValueSetUtilities": {
+					"valueUtilities": {
+						"yes":1,
+						"no":0
+					}
+				}
+			},
+			"permanent contract": {
+				"DiscreteValueSetUtilities": {
+					"valueUtilities": {
+						"yes":1,
+						"no":0
+					}
+				}
+			},
+			"career development opportunities": {
+				"DiscreteValueSetUtilities": {
+					"valueUtilities": {
+						"low":0,
+						"medium":0.5,
+						"high":1
+					}
+				}
+			}
+		},
+		"issueWeights": {
+			"salary": 0.24,
+			"fte": 0.32,
+			"work from home": 0.18,
+			"lease car": 0.06,
+			"permanent contract": 0.16,
+			"career development opportunities": 0.04
+		},
+		"domain": {
+			"name": "jobs",
+			"issuesValues": {
+				"salary": {
+					"values": [
+						"2000",
+						"2500",
+						"3000",
+						"3500",
+						"4000"
+					]
+				},
+				"fte": {
+					"values": [
+						"0.6",
+						"0.8",
+						"1.0"
+					]
+				},
+				"work from home": {
+					"values": [
+						"0",
+						"1",
+						"2"
+					]
+				},
+				"lease car": {
+					"values": [
+						"yes",
+						"no"
+					]
+				},
+				"permanent contract": {
+					"values": [
+						"yes",
+						"no"
+					]
+				},
+				"career development opportunities": {
+					"values": [
+						"low",
+						"medium",
+						"high"
+					]
+				}
+			}
+		},
+		"name": "jobs1"
+	}
+}
Index: exampleparties/timedependentparty/test/resources/testprofile.json
===================================================================
--- exampleparties/timedependentparty/test/resources/testprofile.json	(revision 81)
+++ exampleparties/timedependentparty/test/resources/testprofile.json	(revision 81)
@@ -0,0 +1,47 @@
+{
+	"LinearAdditiveUtilitySpace": {
+		"issueUtilities": {
+			"issue2": {
+				"NumberValueSetUtilities": {
+					"lowValue": 12,
+					"lowUtility": 0.3,
+					"highValue": 18,
+					"highUtility": 0.9
+				}
+			},
+			"issue1": {
+				"DiscreteValueSetUtilities": {
+					"valueUtilities": {
+						"issue1value1": 0.2,
+						"issue1value2": 0.8
+					}
+				}
+			}
+		},
+		"issueWeights": {
+			"issue2": 0.4,
+			"issue1": 0.6
+		},
+		"domain": {
+			"name": "test",
+			"issuesValues": {
+				"issue2": {
+					"range": { "low":12,"high":18,"step":1 }
+				},
+				"issue1": {
+					"values": [
+						"issue1value1",
+						"issue1value2"
+					]
+				}
+			}
+		},
+		"name": "testprofile",
+		"reservationBid": {
+			"issuevalues": {
+				"issue1": "issue1value1",
+				"issue2": 12
+			}
+		}
+	}
+}
Index: exampleparties/timedependentparty/timedependentparty/ExtendedUtilSpace.py
===================================================================
--- exampleparties/timedependentparty/timedependentparty/ExtendedUtilSpace.py	(revision 81)
+++ exampleparties/timedependentparty/timedependentparty/ExtendedUtilSpace.py	(revision 81)
@@ -0,0 +1,78 @@
+
+from geniusweb.bidspace.BidsWithUtility import BidsWithUtility
+from geniusweb.bidspace.Interval import Interval
+from geniusweb.bidspace.IssueInfo import IssueInfo
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.Value import Value
+from geniusweb.profile.utilityspace.LinearAdditive import LinearAdditive
+from tudelft.utilities.immutablelist.ImmutableList import ImmutableList
+from decimal import Decimal
+from typing import List
+
+class ExtendedUtilSpace:
+	'''
+	Inner class for TimeDependentParty, made public for testing purposes. This
+	class may change in the future, use at your own risk.
+	'''
+
+	def __init__(self, space:LinearAdditive ) :
+		self._utilspace = space
+		self._bidutils = BidsWithUtility.create(self._utilspace)
+		self._computeMinMax()
+		self._tolerance = self._computeTolerance()
+
+	def _computeMinMax(self):
+		'''
+		Computes the fields minutil and maxUtil.
+		<p>
+		TODO this is simplistic, very expensive method and may cause us to run
+		out of time on large domains.
+		<p>
+		Assumes that utilspace and bidutils have been set properly.
+		'''
+		range = self._bidutils.getRange()
+		self._minUtil = range.getMin()
+		self._maxUtil = range.getMax()
+
+		rvbid = self._utilspace.getReservationBid()
+		if rvbid != None:
+			rv = self._utilspace.getUtility(rvbid)
+			if rv > self._minUtil:
+				self._minUtil = rv
+
+	def _computeTolerance(self)->Decimal :
+		'''
+		Tolerance is the Interval we need when searching bids. When we are close
+		to the maximum utility, this value has to be the distance between the
+		best and one-but-best utility.
+		
+		@return the minimum tolerance required, which is the minimum difference
+		        between the weighted utility of the best and one-but-best issue
+		        value.
+		'''
+		tolerance = Decimal(1)
+		for iss in self._bidutils.getInfo():
+			if iss.getValues().size()>1:
+				# we have at least 2 values.
+				values:List[Decimal] = []
+				for val in iss.getValues():
+					values.append(iss.getWeightedUtil(val))
+				values.sort()
+				values.reverse()
+				tolerance = min(tolerance, values[0]- values[1])
+		return tolerance;
+
+	def getMin(self)->Decimal:
+		return self._minUtil
+
+	def getMax(self)-> Decimal:
+		return self._maxUtil;
+
+	def getBids(self,  utilityGoal:Decimal) -> ImmutableList[Bid] :
+		'''
+		@param utilityGoal the requested utility
+		@return bids with utility inside [utilitygoal-{@link #tolerance},
+		        utilitygoal]
+		'''
+		return self._bidutils.getBids(
+			Interval(utilityGoal-self._tolerance, utilityGoal))
Index: exampleparties/timedependentparty/timedependentparty/TimeDependentParty.py
===================================================================
--- exampleparties/timedependentparty/timedependentparty/TimeDependentParty.py	(revision 81)
+++ exampleparties/timedependentparty/timedependentparty/TimeDependentParty.py	(revision 81)
@@ -0,0 +1,303 @@
+import logging
+from random import randint, random
+import traceback
+from typing import cast, Dict, List, Set, Collection
+
+from geniusweb.actions.Accept import Accept
+from geniusweb.actions.Action import Action
+from geniusweb.actions.LearningDone import LearningDone
+from geniusweb.actions.Offer import Offer
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.actions.Vote import Vote
+from geniusweb.actions.Votes import Votes
+from geniusweb.bidspace.AllBidsList import AllBidsList
+from geniusweb.inform.ActionDone import ActionDone
+from geniusweb.inform.Finished import Finished
+from geniusweb.inform.Inform import Inform
+from geniusweb.inform.OptIn import OptIn
+from geniusweb.inform.Settings import Settings
+from geniusweb.inform.Voting import Voting
+from geniusweb.inform.YourTurn import YourTurn
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.Domain import Domain
+from geniusweb.issuevalue.Value import Value
+from geniusweb.issuevalue.ValueSet import ValueSet
+from geniusweb.party.Capabilities import Capabilities
+from geniusweb.party.DefaultParty import DefaultParty
+from geniusweb.profile.utilityspace.UtilitySpace import UtilitySpace
+from geniusweb.profileconnection.ProfileConnectionFactory import ProfileConnectionFactory
+from geniusweb.progress.ProgressRounds import ProgressRounds
+from geniusweb.utils import val
+from geniusweb.profileconnection.ProfileInterface import ProfileInterface
+from geniusweb.profile.utilityspace.LinearAdditive import LinearAdditive
+from geniusweb.progress.Progress import Progress
+from tudelft.utilities.immutablelist.ImmutableList import ImmutableList
+from time import sleep,  time as clock
+from decimal import Decimal
+import sys
+from timedependentparty.ExtendedUtilSpace import ExtendedUtilSpace
+from tudelft_utilities_logging.Reporter import Reporter
+
+class TimeDependentParty (DefaultParty):
+    """
+    General time dependent party. This is a simplistic implementation that does
+    brute-force search through the bidspace and can handle bidspace sizes up to
+    2^31 (approx 1 billion bids). It may take excessive time and run out of time
+    on bidspaces &gt; 10000 bids. In special cases it may even run out of memory,
+    
+    <p>
+    Supports parameters as follows
+    <table>
+    <caption>parameters</caption>
+    <tr>
+    <td>e</td>
+    <td>e determines how fast the party makes concessions with time. Typically
+    around 1. 0 means no concession, 1 linear concession, &gt;1 faster than
+    linear concession.</td>
+    </tr>
+    
+    <tr>
+    <td>minPower</td>
+    <td>This value is used as minPower for placed {@link Vote}s. Default value is
+    1.</td>
+    </tr>
+    
+    <tr>
+    <td>maxPower</td>
+    <td>This value is used as maxPower for placed {@link Vote}s. Default value is
+    infinity.</td>
+    </tr>
+    
+    <tr>
+    <td>delay</td>
+    <td>The average time in seconds to wait before responding to a YourTurn. The
+    actual waiting time will be random in [0.5*time, 1.5*time]. This can be used
+    to simulate human users that take thinking time.</td>
+    </tr>
+    
+    </table>
+    <p>
+    TimeDependentParty requires a {@link UtilitySpace}
+    """
+    def __init__(self, reporter:Reporter=None):
+        super().__init__(reporter)
+        self._profileint:ProfileInterface = None #ttype:ignore
+        self._utilspace:LinearAdditive  = None # last received space
+        self._me:PartyId =None #ttype:ignore
+        self._progress:Progress =None #ttype:ignore
+        self._lastReceivedBid:Bid = None
+        self._extendedspace:ExtendedUtilSpace =None
+        self._e:float = 1.2
+        self._lastvotes:Votes =None
+        self._settings:Settings =None
+        self.getReporter().log(logging.INFO,"party is initialized")
+
+    # Override
+    def getCapabilities(self) -> Capabilities:
+        return Capabilities( set([ "SAOP", "Learn", "MOPAC"]), 
+            set(['geniusweb.profile.utilityspace.LinearAdditive']))
+
+    # Override
+    def notifyChange(self, info: Inform):
+        try:
+            if isinstance(info,Settings):
+                self._settings = info
+                self._me = self._settings.getID()
+                self._progress = self._settings.getProgress()
+                newe = self._settings.getParameters().get("e")
+                if newe != None:
+                    if isinstance(newe,float):
+                        self._e = newe
+                    else:
+                        self.getReporter().log(logging.WARNING,
+                                "parameter e should be Double but found "
+                                        + str(newe))
+                protocol:str = str(self._settings.getProtocol().getURI())
+                if "Learn"==protocol:
+                    val(self.getConnection()).send(LearningDone(self._me))
+                else:
+                    self._profileint = ProfileConnectionFactory.create(
+                            self._settings.getProfile().getURI(), self.getReporter())
+
+            elif isinstance(info,ActionDone):
+                otheract:Action  = info.getAction()
+                if isinstance(otheract,Offer):
+                    self._lastReceivedBid = otheract.getBid()
+            elif isinstance(info ,YourTurn):
+                self._delayResponse()
+                self._myTurn()
+            elif isinstance(info ,Finished):
+                self.getReporter().log(logging.INFO, "Final ourcome:" + str(info))
+                self.terminate(); # stop this party and free resources.
+            elif isinstance(info, Voting):
+                lastvotes = self._vote(info);
+                val(self.getConnection()).send(lastvotes)
+            elif isinstance(info, OptIn):
+                val(self.getConnection()).send(lastvotes)
+        except Exception as ex:
+            self.getReporter().log(logging.CRITICAL, "Failed to handle info", ex)
+        self._updateRound(info);
+
+     
+    def getE(self) -> float:
+        '''
+        @return the E value that controls the party's behaviour. Depending on the
+                value of e, extreme sets show clearly different patterns of
+               behaviour [1]:
+        
+               1. Boulware: For this strategy e &lt; 1 and the initial offer is
+                maintained till time is almost exhausted, when the agent concedes
+                up to its reservation value.
+        
+                2. Conceder: For this strategy e &gt; 1 and the agent goes to its
+                reservation value very quickly.
+        
+                3. When e = 1, the price is increased linearly.
+        
+                4. When e = 0, the agent plays hardball.
+        '''
+        return self._e
+    
+    # Override
+    def getDescription(self) -> str:
+        return "Time-dependent conceder. Aims at utility u(t) = scale * t^(1/e) " \
+                + "where t is the time (0=start, 1=end), e is the concession speed parameter (default 1.1), and scale such that u(0)=minimum and " \
+                + "u(1) = maximum possible utility. Parameters minPower (default 1) and maxPower (default infinity) are used " \
+                + "when voting"
+
+    # Override
+    def terminate(self):
+        self.getReporter().log(logging.INFO,"party is terminating:")
+        super().terminate()
+        if self._profileint != None:
+            self._profileint.close()
+            self._profileint = None
+
+    ##################### private support funcs #########################
+
+    def _updateRound(self, info:Inform ):
+        '''
+        Update {@link #progress}, depending on the protocol and last received
+        {@link Inform}
+        
+        @param info the received info.
+        '''
+        if self._settings == None: # not yet initialized
+            return
+        protocol:str = str(self._settings.getProtocol().getURI())
+
+        if "SAOP"==protocol or "SHAOP"==protocol:
+            if not isinstance(info, YourTurn):
+                return
+        elif "MOPAC"==protocol:
+            if not isinstance(info , OptIn):
+                return
+        else:
+            return
+        # if we get here, round must be increased.
+        if isinstance(self._progress , ProgressRounds):
+            self._progress = self._progress.advance()
+    
+    def _myTurn(self):
+        self._updateUtilSpace();
+        bid = self._makeBid()
+
+        myAction:Action 
+        if bid == None or (self._lastReceivedBid != None
+                and self._utilspace.getUtility(self._lastReceivedBid)
+                        >= self._utilspace.getUtility(bid)):
+            # if bid==null we failed to suggest next bid.
+            myAction = Accept(self._me, self._lastReceivedBid)
+        else :
+            myAction = Offer(self._me, bid)
+        self.getConnection().send(myAction)
+
+    def _updateUtilSpace(self)-> LinearAdditive:# throws IOException 
+        newutilspace = self._profileint.getProfile()
+        if not newutilspace==self._utilspace:
+            self._utilspace = cast(LinearAdditive, newutilspace)
+            self._extendedspace = ExtendedUtilSpace(self._utilspace)
+        return self._utilspace
+
+     
+    def _makeBid(self)->Bid :
+        '''
+         @return next possible bid with current target utility, or null if no such
+                 bid.
+        '''
+        time = self._progress.get(round(clock()*1000))
+
+        utilityGoal = self._getUtilityGoal(time, self.getE(),
+                self._extendedspace.getMin(), self._extendedspace.getMax())
+        options:ImmutableList[Bid]  = self._extendedspace.getBids(utilityGoal)
+        if options.size() == 0:
+            # if we can't find good bid, get max util bid....
+            options = self._extendedspace.getBids(self._extendedspace.getMax())
+        # pick a random one.
+        return options.get(randint(0,options.size()-1))
+
+
+    def _getUtilityGoal(self, t:float, e:float, minUtil:Decimal,
+            maxUtil:Decimal) ->Decimal  :
+        '''
+        @param t       the time in [0,1] where 0 means start of nego and 1 the
+                       end of nego (absolute time/round limit)
+        @param e       the e value that determinses how fast the party makes
+                       concessions with time. Typically around 1. 0 means no
+                       concession, 1 linear concession, &gt;1 faster than linear
+                       concession.
+        @param minUtil the minimum utility possible in our profile
+        @param maxUtil the maximum utility possible in our profile
+        @return the utility goal for this time and e value
+        '''
+
+        ft1 = Decimal(1);
+        if e != 0:
+            ft1 = round(Decimal(1 - pow(t, 1 / e)),6) # defaults ROUND_HALF_UP
+        return max(min((minUtil + (maxUtil-minUtil)*ft1), maxUtil), minUtil)
+
+    def _vote(self,  voting:Voting) -> Votes: # throws IOException
+        '''
+        @param voting the {@link Voting} object containing the options
+
+        @return our next Votes.
+        '''
+        val = self._settings.getParameters().get("minPower")
+        # max utility requires smallest possible group/power
+        minpower = val if isinstance(val,int) else 1
+        val = self._settings.getParameters().get("maxPower")
+        maxpower = val if isinstance(val ,int) else sys.maxsize
+
+        votes:Set[Vote]  = { Vote(self._me, offer.getBid(), minpower, maxpower)
+            for offer in set(voting.getOffers()) 
+            if self._isGood(offer.getBid()) }
+        
+        return Votes(self._me, votes)
+
+
+    
+
+    def _isGood(self, bid:Bid) -> bool:
+        '''
+        @param bid the bid to check
+        @return true iff bid is good for us.
+        '''
+        if bid == None or self._profileint == None:
+            return False
+        profile = cast(LinearAdditive, self._profileint.getProfile())
+        # the profile MUST contain UtilitySpace
+        time = self._progress.get(round(clock()*1000))
+        return profile.getUtility(bid) >= \
+            self._getUtilityGoal(time, self.getE(), self._extendedspace.getMin(),
+                        self._extendedspace.getMax())
+
+    def _delayResponse(self):# throws InterruptedException
+        '''
+        Do random delay of provided delay in seconds, randomized by factor in
+        [0.5, 1.5]. Does not delay if set to 0.
+        
+        @throws InterruptedException
+        '''
+        delay = self._settings.getParameters().getDouble("delay", 0, 0, 10000000)
+        if delay > 0:
+            sleep(delay * (0.5 + random()))
