# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_utils.ipynb.

# %% auto 0
__all__ = ['GATEWAY_MAP', 'parse_response', 'parse_error_message', 'IPFSGateway']

# %% ../nbs/00_utils.ipynb 3
import requests
import json
from fastcore.all import *
import pandas as pd
import dag_cbor
from typing import Union
import time

# %% ../nbs/00_utils.ipynb 5
def parse_response(
    response, # Response object
):
    "Parse response object into JSON"
    
    if response.text.split('\n')[-1] == "":
        try:
            return [json.loads(each) for each in response.text.split('\n')[:-1]]
        
        except:
            pass

    try:
        return response.json()

    except:
        return response.text
    
    
def parse_error_message(
    response, # Response object from requests
):
    'Parse error message for raising exceptions'
 
    sc = response.status_code
    
    try:
        message = response.json()['Message']
        
    except:
        message = response.text
    
    return f"Response Status Code: {sc}; Error Message: {message}"

# %% ../nbs/00_utils.ipynb 6
GATEWAY_MAP = {
    'local': [f"http://127.0.0.1:5001"],
    'infura': ['https://ipfs.infura.io:5001'],
    'public': ["https://ipfs.io",
               "https://gateway.pinata.cloud",
               "https://cloudflare-ipfs.com",
               "https://dweb.link"]
}

# %% ../nbs/00_utils.ipynb 7
class IPFSGateway:
    def __init__(self, url):
        self.url = url
        self.state = "unknown"
        self.min_backoff = 1e-9
        self.max_backoff = 5
        self.backoff_time = 0
        self.next_request_time = time.monotonic()
        self.session = requests.Session()
        adapter = requests.adapters.HTTPAdapter(pool_connections=100, pool_maxsize=100)
        self.session.mount('http://', adapter)
        self.session.mount('https://', adapter)

    def get(self, path):
        try:
            res = self.session.get(self.url + "/ipfs/" + path)
        except requests.ConnectionError as e:
            self._backoff()
            return None
        # this is from https://blog.petrzemek.net/2018/04/22/on-incomplete-http-reads-and-the-requests-library-in-python/
        expected_length = res.headers.get('Content-Length')
        if expected_length is not None:
            actual_length = res.raw.tell()
            expected_length = int(expected_length)
            if actual_length < expected_length:
                # if less than the expected amount of data is delivered, just backoff which will will eiter trigger a
                # retry on the same server or will fall back to another server later on.
                self._backoff()
                return None

        if res.status_code == 429:  # too many requests
            self._backoff()
            return None
        elif res.status_code == 200:
            self._speedup()
        res.raise_for_status()
        return res.content

    def head(self, path, headers=None):
        try:
            res = self.session.get(self.url + "/ipfs/" + path)
        except requests.ConnectionError as e:
            self._backoff()
            return None
        if res.status_code == 429:  # too many requests
            self._backoff()
            return None
        elif res.status_code == 200:
            self._speedup()
        res.raise_for_status()
        return res.headers

    def apipost(self, call, **kwargs):
        try:
            data = kwargs.pop('data') if 'data'  in kwargs else {}
            headers = kwargs.pop('headers') if 'headers' in kwargs else {}
            files = kwargs.pop('files') if 'files' in kwargs else {}
            params = kwargs['params'] if 'params' in kwargs else kwargs

            res = self.session.post(self.url + "/api/v0/" + call, 
                                    params=params, 
                                    data=data, 
                                    headers=headers,
                                    files=files)
        
        except requests.ConnectionError:
            self._backoff()
            return None
        
        if res.status_code == 429:  # too many requests
            self._backoff()
            return None
        
        elif res.status_code == 200:
            self._speedup()
        
        return res

    def _schedule_next(self):
        self.next_request_time = time.monotonic() + self.backoff_time

    def _backoff(self):
        self.backoff_time = min(max(self.min_backoff, self.backoff_time) * 2,
                                self.max_backoff)
        self._schedule_next()

    def _speedup(self):
        self.backoff_time = max(self.min_backoff, self.backoff_time * 0.9)
        self._schedule_next()

    def _init_state(self):
        try:
            res = self.session.post(self.url + "/api/v0/version")
            if res.ok:
                self.state = "online"
            else:
                self.state = "offline"
        except requests.ConnectionError:
            self.state = "offline"

    def get_state(self):
        if self.state == "unknown":
            self._init_state()
        now = time.monotonic()
        if self.next_request_time > now:
            return ("backoff", self.next_request_time - now)
        else:
            return (self.state, None)
