#! /usr/bin/env python

import hashlib
import json
import re 
import os
import sys
import argparse
import requests
import urllib.request as req
from bs4 import BeautifulSoup
from bs4.dammit import EncodingDetector
from urllib.parse import urlencode
from PIL import Image
from slugify import slugify



def get_tile(url):
    """Download specific tile designated by URL
    First checks if it's already downloaded, if it is and is not corrupt, return the PIL image
    If image is not yet downloaded or is corrupt, download and return PIL image.

    Args:
        url (str): The url that points to the image to be downloaded

    Returns:
        PIL.Image: The image that was downloaded or loaded.
    """
    hash_name = hashlib.md5(url.encode("utf-16")).hexdigest()
    fname = hash_name + ".jpeg"
    print("Checking tile" + fname)
    #if image is already downloaded, return it
    if os.path.isfile(fname):
        print("Downloaded!")
        try:
            # image was fully downloaded, good to return
            return Image.open(fname) 
        except Exception:
            print("Tile is corrupt :(")
            # file is corrupted for some reason, so try to download it
            pass
    print("Downloading " + fname)
    req.urlretrieve(url, fname) 
    return Image.open(fname)
    
def stich(data, title=None):
    """
    This is the function that actually stiches together based on the json file provieded
    
    Args:
        data (dict): The dictionary that describes each the tiles along with information about art

    Returns:
        
    """
    # Get name, list of tiles, width and height
    name   = data["levels"][0]["name"] 
    tiles  = data["levels"][0]["tiles"]
    width  = data["levels"][0]["width"]
    height = data["levels"][0]["height"]

    # Create the directory to place all the downloaded tiles in
    if title: #if title provided, name directory based on that
        dirname = title
    else: #if title not provided, generate a name
        dirname = name + str(width) + str(height)
    os.makedirs(dirname, exist_ok=True)
    os.chdir(dirname)

    #Create the empty image based on dimensions
    result = Image.new('RGB', (width, height))
    tile_size = None 

    # actually get the tiles
    for i in tiles:
        image = get_tile(i['url']) #download image
        if not tile_size:
            tile_size = image.size[0] # on the first tile get the image size
        result.paste(im=image, box=(i['x'] * tile_size, i['y'] * tile_size)) # each tile has a number which isn't
                                                                             # it's cooridnate in pixels but it's order. 
                                                                             # To get pixel coordinate just multiply by the size of each tile
    result.save('final.jpeg') # save file in directory
    os.chdir(os.path.join( os.path.dirname( __file__ ), '..' )) # then navigate back up to the base directory

def stich_from_file(jsonname):
    """
    Stich together a file based on a json file downloaded
    
    Args:
        jsonname (str): The name of the json file

    Returns:
    """
    with open(jsonname) as f:
        data = json.loads(f.read())
    stich(data)

def stich_from_id(id, title):
    """
    Stich based on the id from the Van Gogh musuem 
    
    Args:
        id (str): The ID from the Van Gogh museum
        title (str): The title of the artwork

    Returns:
    """
    response = requests.get('https://vangoghmuseum-assetserver.appspot.com/tiles?id=%s' % id)
    data = json.loads(response.text)
    stich(data, title)
    
def get_art(term=None,artist=None,num=1):
    """
    This is basically the main runner. Accepts the input from user and then runs the above functions
    
    Args:
        term (str): The term to search for
        artist (str): The artist to search for
        num (int): The number of artworks to download

    Returns:
    """

    # Following block builds the URL based on the search terms provided by user
    base_url = "https://www.vangoghmuseum.nl/en/search/collection?"
    terms = []
    if term:
        terms.append(("q", term))
    if artist:
        terms.append(("artist", artist))
    terms.append(("pagesize", num))
    
    requesturl = base_url + urlencode(terms)
    #print(requesturl)
        
    resp = requests.get(requesturl)
    http_encoding = resp.encoding if 'charset' in resp.headers.get('content-type', '').lower() else None
    html_encoding = EncodingDetector.find_declared_encoding(resp.content, is_html=True)
    encoding = html_encoding or http_encoding
    soup = BeautifulSoup(resp.content, "html.parser", from_encoding=encoding)

    # Sort through all the urls to find the ones that are links to artworks
    urls = []
    for link in soup.find_all('a', href=True):
        if link['href'].startswith('/en/collection/'):
            urls.append(link['href'])

    for url in urls:
        resp = requests.get('https://www.vangoghmuseum.nl' + url)
        http_encoding = resp.encoding if 'charset' in resp.headers.get('content-type', '').lower() else None
        html_encoding = EncodingDetector.find_declared_encoding(resp.content, is_html=True)
        encoding = html_encoding or http_encoding
        soup = BeautifulSoup(resp.content, "html.parser", from_encoding=encoding)

        title = slugify(soup.find('a', attrs={'name': 'info'}).contents[0])
        data_id = soup.find(attrs={'data-id': re.compile("\d+")})['data-id']

        print('Downloading: %s' % title)
        stich_from_id(data_id, title)

if __name__ == "__main__":
    # Build the argument parser
    parser = argparse.ArgumentParser(description="""Download high quality van gogh
                                                  art by stiching together tiles of
                                                  lower quality images""")
    parser.add_argument("--term", type=str, dest='term', default=None,
                         help="the term to search the site for")
    parser.add_argument("--id", type=str, dest='term', default=None,
                         help="the art id to search for. ex in https://www.vangoghmuseum.nl/en/collection/s0135V1962r would be s0135V1962r")
    parser.add_argument("--artist", type=str, dest='artist', default=None,
                        help="the artist's name to search for")
    parser.add_argument("--num", type=int, dest='num', default=1,
                        help="the number of artworks to download")
    # Execute based on arguments provided
    args = parser.parse_args()
    get_art(args.term, args.artist, args.num)

