# Path: feemodel/config.py
# DIFF_RETARGET_INTERVAL = 2016
# PRIORITYTHRESH = 57600000
# EXPECTED_BLOCK_INTERVAL = 600
# MINRELAYTXFEE = 5000
# def read_default_config():
# 
# Path: feemodel/util.py
# def save_obj(obj, filename, protocol=2):
#     '''Convenience function to pickle an object to disk.'''
#     with open(filename, 'wb') as f:
#         pickle.dump(obj, f, protocol=protocol)
# 
# def load_obj(filename):
#     '''Convenience function to unpickle an object from disk.'''
#     with open(filename, 'rb') as f:
#         obj = pickle.load(f)
#     return obj
# 
# def logexceptions(fn):
#     """Decorator that logs exceptions.
# 
#     Logs all exceptions / stack traces in the decorated function before
#     reraising.
#     """
#     @wraps(fn)
#     def logged_fn(*args, **kwargs):
#         try:
#             return fn(*args, **kwargs)
#         except Exception as e:
#             logger.exception(
#                 "{} in {}.".format(e.__class__.__name__, fn.__name__))
#             raise e
#     return logged_fn
# 
# Path: feemodel/estimate/pools.py
# class PoolsEstimatorNP(SimPoolsNP):
# 
#     def __init__(self):
#         super(PoolsEstimatorNP, self).__init__(None, None, blockrate=None)
#         self.blockstats = {}
# 
#     def start(self, blockrangetuple, stopflag=None, dbfile=MEMBLOCK_DBFILE):
#         logger.info("Beginning NP pool estimation "
#                     "from blockrange({}, {})".format(*blockrangetuple))
#         self._clear_window(blockrangetuple[0])
#         for height in range(*blockrangetuple):
#             if height in self.blockstats:
#                 continue
#             if stopflag and stopflag.is_set():
#                 raise StopIteration("Stop flag set.")
#             memblock = MemBlock.read(height, dbfile=dbfile)
#             if memblock is None:
#                 continue
#             self.update(memblock, is_init=True)
#         self._calc_estimates()
#         logger.info("Finished NP pool estimation.")
# 
#     def update(self, memblock, is_init=False, windowsize=None):
#         sfr_stats = memblock.calc_stranding_feerate()
#         if sfr_stats is None or sfr_stats['altbiasref'] == MINRELAYTXFEE:
#             sfr = MINRELAYTXFEE
#         else:
#             sfr = sfr_stats['sfr']
#         mempoolsize = sum(
#             [entry.size for entry in memblock.entries.values()
#              if entry.feerate >= MINRELAYTXFEE])
#         mempoolsize_remain = sum(
#             [entry.size for entry in memblock.entries.values()
#              if entry.feerate >= MINRELAYTXFEE and
#              not entry.inblock])
#         self.blockstats[memblock.blockheight] = (
#             mempoolsize, mempoolsize_remain, memblock.time,
#             memblock.blocksize, sfr)
#         if windowsize:
#             height_thresh = memblock.blockheight - windowsize + 1
#             self._clear_window(height_thresh)
#         if not is_init:
#             self._calc_estimates()
# 
#     def _clear_window(self, height_thresh):
#         for height in self.blockstats.keys():
#             if height < height_thresh:
#                 del self.blockstats[height]
# 
#     def _calc_estimates(self, blockmingap=300, tailpct=0.1):
#         if len(self.blockstats) < 2:
#             # TODO: better checks, taking into consideration tailpct
#             # raise ValueError("Not enough blocks.")
#             return
#         startheight = min(self.blockstats)
#         endheight = max(self.blockstats)
# 
#         # Calculate blockrate
#         numhashes = None
#         totalhashes = 0
#         for height in range(startheight, endheight+1):
#             if numhashes is None or not height % DIFF_RETARGET_INTERVAL:
#                 # TODO: store the numhashes in self.blockstats, so that
#                 #       the block can be pruned if necessary
#                 numhashes = get_hashesperblock(height)
#             totalhashes += numhashes
#         starttime = self.blockstats[startheight][2]
#         endtime = self.blockstats[endheight][2]
#         hashrate = totalhashes / (endtime - starttime)
#         self.blockrate = hashrate / numhashes
# 
#         prevtime = None
#         prevheight = None
#         prevmempoolremain = None
#         blockstats = []
#         for height, blockstat in sorted(self.blockstats.items()):
#             if prevheight is not None and (
#                     height == prevheight + 1 and
#                     blockstat[2] > prevtime + blockmingap):
#                 mempooldiff = blockstat[0] - prevmempoolremain
#                 blockstats.append((blockstat[0], mempooldiff,
#                                    blockstat[3], blockstat[4]))
#             prevtime = blockstat[2]
#             prevheight = height
#             prevmempoolremain = blockstat[1]
# 
#         if not blockstats:
#             # This really shouldn't happen at all.
#             raise ValueError("Not enough blocks.")
# 
#         # Calculate minfeerates and maxblocksizes
#         # minfeerates are from the lower t percent of the blocks
#         tailidx = int(ceil(tailpct*len(blockstats)))
#         blockstats.sort()
#         self.minfeerates = map(itemgetter(3), blockstats[:tailidx])
#         blockstats.sort(key=itemgetter(1))
#         self.maxblocksizes = map(itemgetter(2), blockstats[-tailidx:])
#         return blockstats
# 
#     def __str__(self):
#         try:
#             self.check()
#         except ValueError as e:
#             return e.message
#         minfeerates = sorted(self.minfeerates)
#         maxblocksizes = sorted(self.maxblocksizes)
#         s = ("minfeerates: {}\nmaxblocksizes: {}\nblockinterval: {}".
#              format(minfeerates, maxblocksizes, 1 / self.blockrate))
#         return s


import logging
import threading
import os
from copy import copy
from feemodel.config import datadir
from feemodel.util import save_obj, load_obj, logexceptions
from feemodel.estimate.pools import PoolsEstimatorNP
from __future__ import division



logger = logging.getLogger(__name__)

DEFAULT_MINBLOCKS = 432  # 3 days' worth
SAVEFILE = os.path.join(datadir, 'pools.pickle')



# Path: feemodel/config.py
# DIFF_RETARGET_INTERVAL = 2016
# PRIORITYTHRESH = 57600000
# EXPECTED_BLOCK_INTERVAL = 600
# MINRELAYTXFEE = 5000
# def read_default_config():
# 
# Path: feemodel/util.py
# def save_obj(obj, filename, protocol=2):
#     '''Convenience function to pickle an object to disk.'''
#     with open(filename, 'wb') as f:
#         pickle.dump(obj, f, protocol=protocol)
# 
# def load_obj(filename):
#     '''Convenience function to unpickle an object from disk.'''
#     with open(filename, 'rb') as f:
#         obj = pickle.load(f)
#     return obj
# 
# def logexceptions(fn):
#     """Decorator that logs exceptions.
# 
#     Logs all exceptions / stack traces in the decorated function before
#     reraising.
#     """
#     @wraps(fn)
#     def logged_fn(*args, **kwargs):
#         try:
#             return fn(*args, **kwargs)
#         except Exception as e:
#             logger.exception(
#                 "{} in {}.".format(e.__class__.__name__, fn.__name__))
#             raise e
#     return logged_fn
# 
# Path: feemodel/estimate/pools.py
# class PoolsEstimatorNP(SimPoolsNP):
# 
#     def __init__(self):
#         super(PoolsEstimatorNP, self).__init__(None, None, blockrate=None)
#         self.blockstats = {}
# 
#     def start(self, blockrangetuple, stopflag=None, dbfile=MEMBLOCK_DBFILE):
#         logger.info("Beginning NP pool estimation "
#                     "from blockrange({}, {})".format(*blockrangetuple))
#         self._clear_window(blockrangetuple[0])
#         for height in range(*blockrangetuple):
#             if height in self.blockstats:
#                 continue
#             if stopflag and stopflag.is_set():
#                 raise StopIteration("Stop flag set.")
#             memblock = MemBlock.read(height, dbfile=dbfile)
#             if memblock is None:
#                 continue
#             self.update(memblock, is_init=True)
#         self._calc_estimates()
#         logger.info("Finished NP pool estimation.")
# 
#     def update(self, memblock, is_init=False, windowsize=None):
#         sfr_stats = memblock.calc_stranding_feerate()
#         if sfr_stats is None or sfr_stats['altbiasref'] == MINRELAYTXFEE:
#             sfr = MINRELAYTXFEE
#         else:
#             sfr = sfr_stats['sfr']
#         mempoolsize = sum(
#             [entry.size for entry in memblock.entries.values()
#              if entry.feerate >= MINRELAYTXFEE])
#         mempoolsize_remain = sum(
#             [entry.size for entry in memblock.entries.values()
#              if entry.feerate >= MINRELAYTXFEE and
#              not entry.inblock])
#         self.blockstats[memblock.blockheight] = (
#             mempoolsize, mempoolsize_remain, memblock.time,
#             memblock.blocksize, sfr)
#         if windowsize:
#             height_thresh = memblock.blockheight - windowsize + 1
#             self._clear_window(height_thresh)
#         if not is_init:
#             self._calc_estimates()
# 
#     def _clear_window(self, height_thresh):
#         for height in self.blockstats.keys():
#             if height < height_thresh:
#                 del self.blockstats[height]
# 
#     def _calc_estimates(self, blockmingap=300, tailpct=0.1):
#         if len(self.blockstats) < 2:
#             # TODO: better checks, taking into consideration tailpct
#             # raise ValueError("Not enough blocks.")
#             return
#         startheight = min(self.blockstats)
#         endheight = max(self.blockstats)
# 
#         # Calculate blockrate
#         numhashes = None
#         totalhashes = 0
#         for height in range(startheight, endheight+1):
#             if numhashes is None or not height % DIFF_RETARGET_INTERVAL:
#                 # TODO: store the numhashes in self.blockstats, so that
#                 #       the block can be pruned if necessary
#                 numhashes = get_hashesperblock(height)
#             totalhashes += numhashes
#         starttime = self.blockstats[startheight][2]
#         endtime = self.blockstats[endheight][2]
#         hashrate = totalhashes / (endtime - starttime)
#         self.blockrate = hashrate / numhashes
# 
#         prevtime = None
#         prevheight = None
#         prevmempoolremain = None
#         blockstats = []
#         for height, blockstat in sorted(self.blockstats.items()):
#             if prevheight is not None and (
#                     height == prevheight + 1 and
#                     blockstat[2] > prevtime + blockmingap):
#                 mempooldiff = blockstat[0] - prevmempoolremain
#                 blockstats.append((blockstat[0], mempooldiff,
#                                    blockstat[3], blockstat[4]))
#             prevtime = blockstat[2]
#             prevheight = height
#             prevmempoolremain = blockstat[1]
# 
#         if not blockstats:
#             # This really shouldn't happen at all.
#             raise ValueError("Not enough blocks.")
# 
#         # Calculate minfeerates and maxblocksizes
#         # minfeerates are from the lower t percent of the blocks
#         tailidx = int(ceil(tailpct*len(blockstats)))
#         blockstats.sort()
#         self.minfeerates = map(itemgetter(3), blockstats[:tailidx])
#         blockstats.sort(key=itemgetter(1))
#         self.maxblocksizes = map(itemgetter(2), blockstats[-tailidx:])
#         return blockstats
# 
#     def __str__(self):
#         try:
#             self.check()
#         except ValueError as e:
#             return e.message
#         minfeerates = sorted(self.minfeerates)
#         maxblocksizes = sorted(self.maxblocksizes)
#         s = ("minfeerates: {}\nmaxblocksizes: {}\nblockinterval: {}".
#              format(minfeerates, maxblocksizes, 1 / self.blockrate))
#         return s


# Path: feemodel/app/pools.py
import logging
import threading
import os
from copy import copy
from feemodel.config import datadir
from feemodel.util import save_obj, load_obj, logexceptions
from feemodel.estimate.pools import PoolsEstimatorNP

from __future__ import division



logger = logging.getLogger(__name__)

DEFAULT_MINBLOCKS = 432  # 3 days' worth
SAVEFILE = os.path.join(datadir, 'pools.pickle')



class PoolsOnlineEstimator(object):