#!/usr/bin/python3 -u
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright (C) 2014-2015, 2017 Bashton Ltd
# Copyright (C) 2017 JaquerEspeis
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation.
#
# 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, see <http://www.gnu.org/licenses/>.

# IPFS transport for apt.
# This is based on apt-transport-s3:
# https://github.com/BashtonLtd/apt-transport-s3

import hashlib
import os
import sys

import ipfsapi


class IPFS_method():

    __eof = False

    def __init__(self):
        self.send_capabilities()

    def send_capabilities(self):
        self._send(100, {
            'Version': '1.1',
            'Single-Instance': 'true'})

    def send_status(self, headers):
        self._send(102, headers)

    def send_uri_start(self, headers):
        self._send(200, headers)

    def send_uri_done(self, headers):
        self._send(201, headers)

    def send_uri_failure(self, headers):
        self._send(400, headers)

    def _send(self, code, headers):
        message = APTMessage(code, headers)
        sys.stdout.write(message.encode())

    def run(self):
        """Loop through requests on stdin"""
        while True:
            message = self._read_message()
            if message is None:
                return 0
            if message['number'] == 600:
                uri = message['URI'][0]
                filename = message['Filename'][0]
                try:
                    self.fetch(uri, filename)
                except Exception as e:
                    self.send_uri_failure({
                        'URI': uri,
                        'Message': e.__class__.__name__ + ": " + str(e)})
            else:
                return 100

    def _read_message(self):
        """Read an apt message.

        Apt uses for communication with its methods the text protocol similar
        to http. This function parses the protocol messages from stdin.

        """
        if self.__eof:
            return None
        result = {}
        line = sys.stdin.readline()
        while line == '\n':
            line = sys.stdin.readline()
        if not line:
            self.__eof = True
            return None
        s = line.split(" ", 1)
        result['number'] = int(s[0])
        result['text'] = s[1].strip()
        while not self.__eof:
            line = sys.stdin.readline()
            if not line:
                self.__eof = True
                return result
            if line == '\n':
                return result
            (item, value) = line.split(":", 1)
            if not result.get(item):
                result[item] = []
            result[item].append(value.strip())
        return result

    def fetch(self, uri, filename):
        ipfs_file_path = uri.replace('ipfs:', '', 1)

        self.send_status({'URI': uri, 'Message': 'Waiting for stats'})
        ipfs = ipfsapi.connect('127.0.0.1', 5001)
        stat = ipfs.object_stat(ipfs_file_path)
        self.send_uri_start({
            'URI': uri,
            # FIXME We can't get the real size without downloading the file.
            # https://github.com/ipfs/go-ipfs/issues/2071
            # --elopio - 20171203
            'Size': stat['CumulativeSize']})

        # XXX IPFS downloads the file to the current directory.
        # --elopio - 20171203
        cwd = os.getcwd()
        os.chdir(os.path.dirname(filename))
        try:
            ipfs.get(ipfs_file_path)
            os.rename(
                os.path.basename(ipfs_file_path),
                filename)
        finally:
            os.chdir(cwd)

        hash_md5 = hashlib.md5()
        hash_sha256 = hashlib.sha256()
        hash_sha512 = hashlib.sha512()
        with open(filename, 'rb') as fetched_file:
            for chunk in iter(lambda: fetched_file.read(4096), b''):
                hash_md5.update(chunk)
                hash_sha256.update(chunk)
                hash_sha512.update(chunk)

        self.send_uri_done({
            'URI': uri,
            'Filename': filename,
            'Size': os.stat(filename).st_size,
            'MD5-Hash': hash_md5.hexdigest(),
            'MD5Sum-Hash': hash_md5.hexdigest(),
            'SHA256-Hash': hash_sha256.hexdigest(),
            'SHA512-Hash': hash_sha512.hexdigest()})


class APTMessage():

    _MESSAGE_CODES = {
        100: 'Capabilities',
        102: 'Status',
        200: 'URI Start',
        201: 'URI Done',
        400: 'URI Failure',
        600: 'URI Acquire',
        601: 'Configuration'
    }

    def __init__(self, code, headers):
        self._code = code
        self._headers = headers

    def encode(self):
        result = '{0} {1}\n'.format(
            self._code, self._MESSAGE_CODES[self._code])
        for header_key in self._headers:
            if self._headers[header_key] is not None:
                result += '{0}: {1}\n'.format(
                    header_key, self._headers[header_key])
        return result + '\n'


if __name__ == '__main__':
    try:
        method = IPFS_method()
        exitcode = method.run()
        sys.exit(exitcode)
    except KeyboardInterrupt:
        pass