from copy import deepcopy
import random

class AI:
    """
    This class is responsible of calculating the best possible move for a player in a certain game situation.
    """
    def __init__(self, game, player):
        """
        This class gets game Game instance and a player (1 or 2).
        :param game: Game instance
        :param player: 1 / 2
        """
        self.__player = player
        self.__players = [1, 2]
        self.__game = game
        self.__learning_depth = 2
        self.__BOARD_WIDTH = 7
        self.__BOARD_HEIGHT = 6
        self.__current_moves_score = [0]*self.__BOARD_WIDTH
        self.__current_best_move = random.randint(0,6)

    def __is_board_full(self, game):
        """
        This function checks if the board in the given game instance is full.
        The function relies that the get_player_at function will return None for empty cell in the board.
        :param game: Game instance
        :return: True if full, False if not
        """
        board_data = []
        for c in range(self.__BOARD_WIDTH):
            for r in range(self.__BOARD_HEIGHT):
                board_data.append(game.get_player_at(r, c))
        if all(b not in self.__players for b in board_data):
            return True
        return False

    def __calculate_moves_score(self, game=None, current_player=None, depth=None):
        """
        This function is calculating a score for every possible move in the game using recursion.
        Ths function will simulate every possible moves of the current player and every possible move of the
        second player and will check the what did the move cause ( win to the desired player or loss).
        The moves are being simulated by creating a game instance copy which does not affect the given game instance.
        The recursion has a base case = depth value- which is the number of moves ahead that the function will
        calculate.
        The function will add 1 to the score if the user won in this move, and will decrease the score by 1
        if the user lost in this move.
        The function updates a list of the current moves scores in the instance and the current best move
        (the move with the best score).

        :param game: Game instance
        :param current_player: 1 or 2
        :param depth: int (How many moves ahead the function will calculate)
        :return: The moves scores
        """
        if not game:
            game = self.__game
        if not current_player:
            current_player = self.__player
        moves_score = [0] * self.__BOARD_WIDTH
        if depth is None:
            depth = self.__learning_depth
        if depth == 0 or self.__is_board_full(game):
            return moves_score
        enemy_player = [p for p in self.__players if p != current_player][0]
        for m in range(self.__BOARD_WIDTH):
            game_copy = deepcopy(game)
            try:
                game_copy.make_move(m)
                move_result = game_copy.get_winner()
                if move_result == current_player:
                    moves_score[m] += 1
                    break
                else:
                    if move_result == 0:
                        moves_score[m] = 0
                    else:
                        for enemy_m in range(self.__BOARD_WIDTH):
                            game_copy_2 = deepcopy(game_copy)
                            try:
                                game_copy_2.make_move(enemy_m)
                                enemy_move_result = game_copy_2.get_winner()
                                if enemy_move_result == enemy_player:
                                    moves_score[m] -= 1
                                    break
                                else:
                                    score_result = self.__calculate_moves_score(game=game_copy_2,
                                                                                current_player=current_player,
                                                                                depth=depth-1)
                                    # Adding the average score that this move got in the next game loop ( average of all
                                    # the move's score's that were applied after that combination of moves were applied.

                                    moves_score[m] += (sum(score_result) / self.__BOARD_WIDTH) / self.__BOARD_WIDTH
                                    self.__current_moves_score = moves_score
                                    self.__current_best_move = self.__current_moves_score.index(
                                        max(self.__current_moves_score))
                            except BaseException:
                                continue
            except BaseException:
                continue
        return moves_score

    def find_legal_move(self):
        """
        This function calls calculate_moves_score to update the current_moves_score list in the instance.
        The function will loop over the scores that were updated and will try to return the legal move with the
        best score.
        If the no legal moves has the best score the function will return a random legal move.
        :return: The best possible move for the player that is given to the instance.
        """
        if self.__game.get_current_player() != self.__player:
            raise BaseException("Wrong Player")
        self.__calculate_moves_score()
        best_moves = []
        legal_moves = {}
        # Finding the legal moves
        for i, m in enumerate(self.__current_moves_score):
            if self.is_move_valid(i):
                legal_moves[i] = m
        best_move_score = max(legal_moves.values())
        # Finding the best legal moves
        for m in legal_moves.keys():
            if legal_moves[m] == best_move_score:
                best_moves.append(m)
        if best_moves:
            return random.choice(best_moves)
        else:
            raise BaseException("No possible AI moves")

    def is_move_valid(self, col):
        """
        This function gets a move (col index) and checks if the move is legal by checking that the first row
        in the column is empty (index - (0, col)).
        :param col: column index
        :return: True if the move is legal, False if not
        """
        try:
            # Checking the first row of the column
            value = self.__game.get_player_at(0, col)
            if value is None:
                return True
            return False
        # If the col that is given is not in the board
        except IndexError:
            return False

    def get_last_found_move(self):
        """
        :return:This function returns the best current move that is save in the instance arguments
        """
        return self.__current_best_move


import os, boto3

defaultRegion = "us-east-1"
defaultUrl = "https://polly.us-east-1.amazonaws.com"

def connectToPolly(regionName=defaultRegion, endpointUrl=defaultUrl):
    return boto3.client("polly", region_name=regionName, endpoint_url=endpointUrl)

def speak(polly, text, format="mp3", voice="Brian"):
    text = f"<speak><prosody rate=\"fast\">{text}</prosody></speak>"
    resp = polly.synthesize_speech(OutputFormat=format, Text=text, VoiceId=voice, TextType="ssml")
    soundfile = open("/tmp/sound.mp3", "wb")
    soundBytes = resp["AudioStream"].read()
    soundfile.write(soundBytes)
    soundfile.close()
    os.system("afplay /tmp/sound.mp3")  # Works only on Mac OS, sorry
    os.remove("/tmp/sound.mp3")

def speakLive(text):
    polly = connectToPolly()
    speak(polly, text)


from app.models import Project
from flask import Blueprint, render_template, request
from flask_login import current_user, login_required

dash = Blueprint(
    "dashboard", __name__, subdomain="<org_username>", template_folder="templates"
)

@dash.route("/", methods=["GET"])
@login_required
def index(org_username):
    page = request.args.get("page", 1)
    projects = (
        Project.query.filter(Project.permissions.any(member=current_user))
        .filter_by(project_id=None)
        .order_by(Project.id.desc())
        .paginate(int(page), 5)
    )
    return render_template("dashboard/index.html", projects=projects)


import cv2
import numpy as np
import time
from libs.utilities import *
import torch
import sys

from libs.model import ImageNet

smallSize = len(getLowerCase())
bigSize = len(getAll())

#initialize ImageNet
smallModel = ImageNet(smallSize)
bigModel = ImageNet(bigSize)
#load existing weights
bigModel.load_state_dict(torch.load("./models/big.pt"))
bigModel.eval()

smallModel.load_state_dict(torch.load("./models/small.pt"))
smallModel.eval()

if (len(sys.argv) <= 1):
	print("please provide an imagename that is in images/ folder")
	sys.exit(0)
imageName = sys.argv[1]

## read image
img = cv2.imread("./images/"+imageName) #big.jpg

#convert to greyscale and threshold
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
th, threshed = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)

# find bounding rect around "text"
pts = cv2.findNonZero(threshed)
ret = cv2.minAreaRect(pts)

(cx,cy), (w,h), ang = ret

#if (img.shape[0] < img.shape[1]):
#	ang -= 90

if (h > w):
	ang += 90

if (abs(ang) > 90):
	ang = ang % 90

#rotate to be horizontal
M = cv2.getRotationMatrix2D((cx,cy), ang, 1.0)
rotated = cv2.warpAffine(threshed, M, (img.shape[1], img.shape[0]))

#thresh to get rid of artifacts
th, rotated = cv2.threshold(rotated, 127, 255, cv2.THRESH_BINARY )

#cv2.imshow('image',rotated)
#cv2.waitKey(3000)

## find boundaries between lines of text
hist = cv2.reduce(rotated,1, cv2.REDUCE_AVG).reshape(-1)
th = 2
H,W = img.shape[:2]
uppers = [y for y in range(H-1) if hist[y]<=th and hist[y+1]>th]
lowers = [y for y in range(H-1) if hist[y]>th and hist[y+1]<=th]

lines = [] #lines with others than first letters of words
firstLines = [] #lines with first letters of words
spaces = {} #x,y:1 for each space

for i in range(0, len(uppers)):
	line = []
	firstLine = []

	y0 = uppers[i]
	y1 = lowers[i]
	y1+=5
	y0-=1

	#getting line
	roi = rotated[y0:y1, 0:W]

	points = np.nonzero(roi)
	ys = points[0]
	xs = points[1]
	x2, x3, y2, y3 = min(xs), max(xs), min(ys), max(ys)
	roi = roi[y2:y3+1, x2:x3+1]

	if (roi.size == 0):
		continue

	#separating individual letters as they don't touch each other
	contours, hierarchy = cv2.findContours(roi, cv2.RETR_EXTERNAL , cv2.CHAIN_APPROX_NONE)
	contours = sorted(contours, key=lambda ctr: cv2.boundingRect(ctr)[0])

	#calculate average distance between characters on the line, used for separating words
	previousEnd = 0
	cumulativeDistance = 0
	for contour in contours:
		[x,y,w,h] = cv2.boundingRect(contour)
		cumulativeDistance += x - previousEnd
		previousEnd = x+w
	averageDistance = cumulativeDistance / float(len(contours))

	#calculate average size of contour to be able to ignore false detections later
	#  (they are much smaller)
	cumulativeSize = 0
	for contour in contours:
		[x,y,w,h] = cv2.boundingRect(contour)
		cumulativeSize += w*h
	averageSize = cumulativeSize / float(len(contours))

	previousEnd = 0
	j = 0
	for contour in contours:

		[x,y,w,h] = cv2.boundingRect(contour)

		#ignoring contours which are less than x % of average on the line.
		# this will get rid of duplicate detections for letters such as i & j
		size = w*h
		if (size < 0.2 * averageSize):
			continue

		#figuring out the distance to the previous letter
		space = x - previousEnd
		previousEnd = x+w

		#take into account the entire height of the line
		height = roi.shape[0]
		letter = roi[0:height-1, x:x+w]
		points = np.nonzero(letter)

		ys = points[0]
		xs = points[1]

		if (len(ys) < 4): #less than 5 white pixels high, should probably ignore
			continue

		#crop the actual letter and scale to 32x32
		letter = getRoiAs32(letter)
		if (letter.size == 0):
			continue

		if (space > 2*averageDistance):
			#a new word
			spaces[(j, i)] = 1 #
			firstLine.append(letter)
		elif (j == 0):
			#first letter of line
			firstLine.append(letter)
		else:
			#all other letters
			line.append(letter)
		j += 1

	if (len(line) != 0 or len(firstLine) != 0):
		lines.append(line)
		firstLines.append(firstLine)

# going through the lines of text, getting corresponding
# predictions from the neural net
letters = lines
outputs = []
firstOutputs = []

for inputs in lines:
	if (len(inputs) == 0):
		outputs.append([])
		continue
	inputs = np.divide(inputs, 255)
	in2 = []
	for i in inputs:
		in2.append([i])
	inputs = np.array(in2)

	X = torch.from_numpy(inputs).type(torch.FloatTensor)

	out = smallModel(X)
	outputs.append(out.cpu().detach().numpy())

for inputs in firstLines:
	if (len(inputs) == 0):
		firstOutputs.append([])
		continue
	inputs = np.divide(inputs, 255)
	in2 = []
	for i in inputs:
		in2.append([i])
	inputs = np.array(in2)

	X = torch.from_numpy(inputs).type(torch.FloatTensor)

	out = bigModel(X)
	firstOutputs.append(out.cpu().detach().numpy())

#go through both first letters of words, spaces & other letters
#and append them to the screen in the correct order
for j in range(len(outputs)):
	out = outputs[j]
	outf = firstOutputs[j]
	line = ""
	big = 0
	small = 0
	i = 0
	while i < len(out) + len(outf):
		character = ""
		if ((i, j) in spaces):
			line += " "
			index = outf[big]
			charNumber = np.argmax(index)
			character = numToAllChar(charNumber) #numToAllChar
			big += 1
		elif (big == 0):
			index = outf[big]
			charNumber = np.argmax(index)
			character = numToAllChar(charNumber) #numToAllChar
			big += 1
		else:
			index = out[small]
			charNumber = np.argmax(index)
			character = numToLowerCase(charNumber) #numToAllChar
			small += 1
		i += 1
		line += character

	print(line)


import numpy as np
import cv2

from lpf.objectives import Objective

class EachHistogramRootMeanSquareError(Objective):

    def __init__(self, targets=None, coeff=None):
        if not coeff:
            coeff = 1e-02

        self._coeff = coeff

        if targets:
            self._target_hists = self.get_target_histograms(targets)
        else:
            self._target_hists = None

    def get_histogram(self, arr):
        hist_r = cv2.calcHist([arr], [2], None, [256], [0, 256])
        hist_g = cv2.calcHist([arr], [1], None, [256], [0, 256])
        hist_b = cv2.calcHist([arr], [0], None, [256], [0, 256])

        return np.array([hist_r.ravel(), hist_g.ravel(), hist_b.ravel()])

    def get_target_histograms(self, targets):
        hists = []
        for target in targets:
            arr_target = np.array(target, dtype=np.uint8)
            hist = self.get_histogram(arr_target)
            hists.append(hist)

        return hists

    def compute(self, x, targets=None, coeff=None):

        if not self._target_hists:
            if not targets:
                err_msg = "targets should be given for compute() " \
                          "if targets are not given for __init__()."
                raise AttributeError(err_msg)

            target_hists = self.get_target_histograms(targets)
        else:
            target_hists = self._target_hists

        if not coeff:
            coeff = self._coeff

        arr_rmse = np.zeros((len(target_hists),), dtype=np.float64)
        for i, hist_target in enumerate(target_hists):
            hist_source = self.get_histogram(np.array(x))
            arr_rmse[i] = np.sqrt(np.mean((hist_target - hist_source)**2))

        return coeff * arr_rmse

class SumHistogramRootMeanSquareError(EachHistogramRootMeanSquareError):

    def compute(self, *args, **kwargs):
        arr_rmse = super().compute(*args, **kwargs)
        return arr_rmse.sum()

class MeanHistogramRootMeanSquareError(EachHistogramRootMeanSquareError):

    def compute(self, *args, **kwargs):
        arr_rmse = super().compute(*args, **kwargs)
        return np.mean(arr_rmse)

class MinHistogramRootMeanSquareError(EachHistogramRootMeanSquareError):

    def compute(self, *args, **kwargs):
        arr_rmse = super().compute(*args, **kwargs)
        return arr_rmse.min()

class MaxHistogramRootMeanSquareError(Objective):

    def compute(self, *args, **kwargs):
        arr_rmse = super().compute(*args, **kwargs)
        return arr_rmse.max()


# Define a function that iterates over a list of numebrs.
# multiplies each nuber by one less than its index position
# and returns the total sum of those products

values = [1, 2, 3, 4, 5]

# 1 *-1 =-1
# 2 * 0 = 0
# 3 * 1 = 3
# 4 * 2 = 8
# 5 * 3 = 15
# ==========
#          25

def multiply_element_by_one_less_than_index(numbers):
    sum = 0
    for index, number in enumerate(numbers):
        sum += number * (index -1)
    return sum

print(multiply_element_by_one_less_than_index(values))


import time
from selenium.webdriver.common.by import By
from SeleniumDriverCreator import SeleniumDriverCreator
from Constants import Constants
import StaticMethods

def isModelOnline(fansUserName):
    fansUrl = f"https://fansly.com/{fansUserName}"
    thumbUrl = ""
    icon = 'images/errIcon.png'
    isOnline = False
    title = Constants.fansDefaultTitle
    driverCreator = SeleniumDriverCreator()
    driver = driverCreator.createDriver()
    driver.get(fansUrl)
    time.sleep(10)
    checkForEnterButton(driver)
    driver.get_screenshot_as_file("Fansscreenshot.png")
    online = driver.find_elements(By.XPATH, '/html/body/app-root/div/div[1]/div/app-profile-route/div/div/div/div[1]/div[2]/div[1]/app-account-avatar/div')
    iconEle = driver.find_elements(By.TAG_NAME, 'img')
    if len(iconEle) >= 5:
        byte = StaticMethods.get_file_content_chrome(driver, iconEle[4].get_attribute('src'))
        file = open("images/fansIcon.jpg", 'wb')
        file.write(byte)
        file.close()
        icon = "images/fansIcon.jpg"
    driver.quit()
    if len(online) > 0:
        isOnline = True
    return isOnline, title, thumbUrl, icon

def checkForEnterButton(driver):
    button = driver.find_elements(By.XPATH, "/html/body/app-root/div/div[3]/app-age-gate-modal/div/div/div[4]/div/div[2]")
    if len(button) > 0:
        button[0].click()
        time.sleep(10)


import math

import tensorflow as tf
import numpy as np

class Data_loader(object):
    def __init__(self, data_file, batch_size, max_length):
        self.data_file = data_file
        self.batch_size = batch_size
        self.max_length = max_length
        self.vocab_name = 'vocab.txt'
        self._pad, self._bos, self._eos, self._unk = 0, 1, 2, 3
        self.load_data()
        self.generate_batch_number()
        self.reset_batch_pointer()

    def load_data(self):
        self.loaded_data = [line.strip().replace(' ', '') for line in open(self.data_file, 'r')]
        self.data_length = len(self.loaded_data)
        self.load_dictionary()
        self.prepare_text_data()

    def load_dictionary(self):
        print ('loading dictionary')
        self.vocab, self.rev_vocab = {}, {}
        with open(self.vocab_name, 'r') as fin:
            for line in fin:
                i, w = line.strip().split()
                self.vocab[str(w)] = int(i)
                self.rev_vocab[int(i)] = str(w)

    def sentence_to_id(self, sentence):
        return [int(self.vocab.get(w, self._unk)) for w in sentence]

    def prepare_text_data(self):
        text_id = []
        text_weight = []
        for data in self.loaded_data:
            sentence = self.sentence_to_id(data)
            if len(sentence) >= self.max_length:
                text_weight.append(self.max_length)
                text_id.append(sentence[:self.max_length-1] + [self._eos])
            else:
                text_weight.append(len(sentence)+1)
                text_id.append(sentence + [self._eos] + [self._pad]*(self.max_length-1-len(sentence)))
        self.text_id = np.asarray(text_id)
        self.text_weight = np.asarray(text_weight)

    def generate_batch_number(self):
        self.batch_number = (self.data_length-1) // self.batch_size + 1

    def reset_batch_pointer(self):
        self.pointer = 0

    def update_pointer(self):
        if self.data_length - self.pointer <= self.batch_size:
            self.pointer = self.pointer + self.batch_size - self.data_length
        else:
            self.pointer += self.batch_size

    def get_batch(self, test):
        for i in range(self.batch_number):
            if self.data_length - self.pointer < self.batch_size:
                sentence = self.text_id[self.pointer:]
                weight  = self.text_weight[self.pointer:]
                ground_truth = self.add_BOS(sentence, self.data_length - self.pointer)
                yield sentence, weight, ground_truth
            else:
                sentence = self.text_id[self.pointer:self.pointer+self.batch_size]
                weight = self.text_weight[self.pointer:self.pointer+self.batch_size]
                ground_truth = self.add_BOS(sentence, self.batch_size)
                self.update_pointer()
                yield sentence, weight, ground_truth

    def add_BOS(self, caption, size):
        BOS_slice = np.ones([size, 1], dtype=np.int32)*self._bos
        return np.concatenate([BOS_slice, caption[:, :-1]], axis=-1)

class Language_model(object):
    def __init__(self):
        self.hidden_size   = 256
        self.vocab_size    = 3000
        self.max_length    = 30
        self.emb_size      = 256
        self.global_step   = tf.Variable(0, trainable=False, name='global_step')
        self.build_graph()

    def build_graph(self):
        self.ground_truth   = tf.placeholder(tf.int32, shape=[None, self.max_length]) # w/  GO
        self.target_inputs  = tf.placeholder(tf.int32, shape=[None, self.max_length]) # w/o GO
        self.target_weights = tf.placeholder(tf.int32, shape=[None])

        with tf.variable_scope('encoder'):
            self.log_prob = encoder(self.ground_truth, self.hidden_size, self.vocab_size)

        with tf.variable_scope('loss'):
            self.loss = sequence_loss(self.log_prob, self.target_inputs, self.target_weights)

def encoder(encoder_inputs, hidden_size, vocab_size):
    with tf.variable_scope('encoder_rnn'):
        # cell
        encoder_cell = tf.contrib.rnn.BasicLSTMCell(hidden_size)
        # embedding
        init = tf.contrib.layers.xavier_initializer()
        word_embedding = tf.get_variable(name='embedding', shape=[vocab_size, hidden_size], initializer=init)
        encoder_inputs = tf.nn.embedding_lookup(word_embedding, encoder_inputs)
        # rnn
        encoder_outputs, encoder_state = tf.nn.dynamic_rnn(
                cell = encoder_cell,
                inputs = encoder_inputs,
                dtype = tf.float32)
        log_prob = tf.layers.dense(encoder_outputs, vocab_size, name='output_projection')
    return log_prob

def sequence_loss(decoder_outputs, target_inputs, target_weights):
    loss = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=target_inputs, logits=decoder_outputs)
    mask = tf.sequence_mask(target_weights, tf.shape(target_inputs)[1])
    loss = tf.where(mask, loss, tf.zeros_like(loss))
    mean_loss = tf.reduce_sum(loss) / tf.cast(tf.reduce_sum(target_weights), tf.float32)
    return mean_loss

class AverageMeter(object):
    def __init__(self):
        self.reset()

    def reset(self):
        self.val = 0
        self.avg = 0
        self.sum = 0
        self.count = 0

    def update(self, val, n=1):
        self.val = val
        self.sum += val * n
        self.count += n
        self.avg = self.sum / self.count

def test(sess, s, data_loader):
    print ('Testing...')
    epoch_loss = AverageMeter()
    for target_inputs, target_weights, ground_truth in data_loader.get_batch(True):
        feed_dict = {
                    s.ground_truth   :ground_truth,
                    s.target_inputs  :target_inputs,
                    s.target_weights :target_weights
        }
        [loss] = sess.run([s.loss], feed_dict=feed_dict)
        epoch_loss.update(loss, np.sum(target_weights))
    return math.exp(epoch_loss.avg)


import scrapy
from scrapy.loader import ItemLoader
from tutorial.items import QuoteItem

class QuoteSpider(scrapy.Spider):
    name = 'quotes'

    start_urls = ['http://quotes.toscrape.com']

    def parse(self, response):
        # self.logger.info('hello this is my first spider')
        quotes = response.css('div.quote')
        for quote in quotes:
            loader = ItemLoader(item = QuoteItem(), selector = quote)

            # load data to item objects
            loader.add_xpath('quote_content', ".//span[@class='text']/text()")
            loader.add_css('tags', '.tag::text')
            quote_item = loader.load_item()
            # get url to author page
            author_url = quote.css('.author+a::attr(href)').get()
            # go to the author page and pass the collected quote info
            yield response.follow(author_url, self.parse_author, meta = {'quote_item': quote_item})

        # go to next page
        for a in response.css('li.next a'):
            yield response.follow(a, self.parse)

# get author information from author page
    def parse_author(self, response):
        # load collected item from first page
        quote_item = response.meta['quote_item']
        loader = ItemLoader(item = quote_item, response = response)
        loader.add_css('author_name', '.author-title::text')
        loader.add_css('author_birthday', '.author-born-date::text')
        loader.add_css('author_born_location', '.author-born-location::text')
        loader.add_css('author_bio', '.author-description::text')
        yield loader.load_item()


import json
from io import BytesIO
try:
    import unittest2 as unittest
except ImportError:
    import unittest

import ckanapi

UPLOAD_DATA = b"""
public info
""".lstrip()

def wsgi_app(environ, start_response):
    status = '200 OK'
    headers = [('Content-type', 'application/json')]

    path = environ['PATH_INFO']
    if path == '/api/action/hello_world':
        response = {'success': True, 'result': 'how are you?'}
    elif path == '/api/action/invalid':
        response = {'success': False, 'error': {'__type': 'Validation Error'}}
    elif path == '/api/action/echo':
        content = environ['wsgi.input'].read()
        response = {'success': True, 'result':
            json.loads(content)['message']}
    elif path == '/api/action/upload':
        # poor man's multipart parsing
        data = environ['wsgi.input'].read().split('\r\n')
        saw_file = False
        for i, s in enumerate(data):
            if saw_file:
                if s == b'':
                    break
            elif b'filename="f"' in s:
                saw_file = True
        response = {'success': True, 'result': data[i + 1].decode('ascii')}
    elif path == '/api/action/not_ckan':
        response = {'success': False, 'error': True}

    start_response(status, headers)
    return [json.dumps(response)]

class TestTestAPPCKAN(unittest.TestCase):
    def setUp(self):
        try:
            import paste.fixture
        except ImportError:
            raise unittest.SkipTest('paste not importable')
        self.test_app = paste.fixture.TestApp(wsgi_app)
        self.ckan = ckanapi.TestAppCKAN(self.test_app)

    def test_simple(self):
        self.assertEqual(
            self.ckan.action.hello_world(), 'how are you?')

    def test_invalid(self):
        self.assertRaises(
            ckanapi.ValidationError,
            self.ckan.action.invalid)

    def test_data(self):
        self.assertEqual(
            self.ckan.action.echo(message='for you'), 'for you')

    def test_upload_action(self):
        self.assertEqual(
            self.ckan.action.upload(package_id='42',
                f=BytesIO(UPLOAD_DATA)), 'public info\n')

    def test_not_ckan(self):
        self.assertRaises(
            ckanapi.ServerIncompatibleError,
            self.ckan.action.not_ckan)


import copy
import json
import os
import urllib.request
from typing import Optional, Union, Mapping

from .cardformatter import fix_dict
from .cards import *

class HearthcardsCardDownloader(object):
    def __init__(self, set_num=None):
        """
        Downloads all the cards from the Hearthcards gallery, or downloads a specific set.

        Behaves like a generator.
        :param set_num: When set, downloads a specific set.
        """
        self._item_id = set_num
        if set_num is not None:
            self._getter = self._get_set_page
        else:
            self._getter = self._get_gallery_page
        self._pages = {}
        self._get_sizes()

    def __iter__(self):
        for x in range(1, self._max_pages + 1):
            if x not in self._pages:
                self._pages[x] = self._getter(page=x)
            for card in self._pages[x]['cards']:
                yield card

    def __len__(self):
        return self._max_cards

    def _get_sizes(self):
        data = self._getter(page=1)
        self._pages[1] = data
        self._max_pages = data['main']['maxpage'] if 'maxpage' in data['main'] else 1
        self._max_cards = int(data['main']['total']) \
            if 'total' in data['main'] \
            else int(data['main']['cardsAmount'])

    def _get_gallery_page(self, page=1):
        link = 'http://www.hearthcards.net/gallery/json_custom.php?class=&mana=all&page=' + str(
            page) + '&collection=0&sort=date&deckid=&language=en&search=&f=1'
        f = urllib.request.urlopen(link)
        data = json.loads(f.read())
        return data

    def _get_set_page(self, page=1):
        link = 'http://www.hearthcards.net/setsandclasses/ajax/ajax_set_hero.php?setid=' + str(
            self._item_id)
        f = urllib.request.urlopen(link)
        data = json.loads(f.read())
        return data

def from_hearthcard_to_spellsource(card, set='CUSTOM', hero_class='WHITE', **kwargs):
    """
    Converts a Hearthcard card dict to a Spellsource card,
    :param card: The Hearthcards card dict to convert
    :param set: The set, defaulting to 'CUSTOM'
    :param hero_class: The hero class override
    :param kwargs: All other overrides
    :return:
    """
    card_attack = card['attack']
    card_health = card['health']
    if 'type' in card:
        card_type = card['type'].upper()
    else:
        if card_attack == '0' and card_health == '0':
            card_type = 'SPELL'
        else:
            card_type = 'MINION'
    if card_type == 'HEROPOWER':
        card_type = 'HERO_POWER'
    out = {}
    out['name'] = card['cardname']
    out['set'] = set
    out['description'] = card['cardtext']
    description = out['description'].lower()
    out['type'] = card_type
    out['rarity'] = card['rarity'].upper()
    if card['class'].upper() in CLASS_MAPPING:
        out['heroClass'] = CLASS_MAPPING[card['class'].upper()]
    else:
        out['heroClass'] = hero_class
    out['baseManaCost'] = int(card['mana'])

    enrich_from_description(out, description, card_attack, card_health, card_type)
    out['collectible'] = True
    out['fileFormatVersion'] = 1
    if kwargs is not None:
        out.update(kwargs)
    return out

def enrich_from_description(card_dict: Mapping,
                            description: Optional[str] = None,
                            card_attack: Optional[Union[str, int]] = None,
                            card_health: Optional[Union[str, int]] = None,
                            card_type: Optional[str] = None,
                            hero_class: Optional[str] = None):
    if description is None:
        if 'description' in card_dict:
            description = card_dict['description']

    # Remove Hearthcards tags and newlines from description
    card_dict['description'] = description \
        .replace('[b]', '') \
        .replace('[/b]', '') \
        .replace('[i]', '') \
        .replace('[/i]', '') \
        .replace('\r\n', ' ') \
        .replace('\n', ' ')

    description = description.lower()

    if card_type is None:
        if 'type' in card_dict:
            card_type = card_dict['type']
        if 'cardType' in card_dict:
            card_type = card_dict['cardType']

    if card_attack is None:
        if 'baseAttack' in card_dict:
            card_attack = card_dict['baseAttack']
        elif 'damage' in card_dict:
            card_attack = card_dict['damage']

    if card_health is None:
        if 'baseHp' in card_dict:
            card_health = card_dict['baseHp']
        elif 'durability' in card_dict:
            card_health = card_dict['durability']

    card_dict['attributes'] = attributes = {}
    spell = {
        'class': 'BuffSpell',
        'target': 'SELF',
        'attackBonus': 0,
        'hpBonus': 0
    }
    spells = []
    if re.search(r'costs \(\d+\) (more)|(less)', description):
        card_dict['manaCostModifier'] = {
            'class': 'PlayerAttributeValueProvider',
            'playerAttribute': 'HAND_COUNT'
        }
    if 'give' in description or 'gain' in description:
        spell = {
            'class': 'BuffSpell',
            'target': 'SELF',
            'attackBonus': 0,
            'hpBonus': 0
        }
        spells += [copy.deepcopy(spell)]
    if 'deal' in description and 'damage' in description:
        spell = {
            'class': 'DamageSpell',
            'value': 1
        }
        spells += [copy.deepcopy(spell)]
    if 'recruit' in description and 'silver hand' not in description:
        spell = {
            'class': 'RecruitSpell',
            'cardLocation': 'DECK',
            'cardFilter': {
                'class': 'CardFilter',
                'cardType': 'MINION',
                'manaCost': 1
            }
        }
        spells += [copy.deepcopy(spell)]
    if 'discard' in description:
        spell = {
            'class': 'DiscardSpell',
            'value': 1
        }
        spells += [copy.deepcopy(spell)]
    if 'add' in description and 'to' in description and 'hand' in description:
        spell = {
            'class': 'ReceiveCardSpell',
            'card': 'spell_mirror_image'
        }
        spells += [copy.deepcopy(spell)]
    if 'summon' in description:
        spell = {
            'class': 'SummonSpell',
            'card': 'minion_bloodfen_raptor'
        }
        spells += [copy.deepcopy(spell)]
    if 'draw' in description:
        if 'whenever you draw' not in description:
            spell = {
                'class': 'DrawCardSpell',
                'value': 1
            }
            spells += [copy.deepcopy(spell)]
    if 'shuffle' in description:
        spell = {
            'class': 'ShuffleToDeckSpell',
            'value': 1,
            'card': 'minion_bloodfen_raptor'
        }
        spells += [copy.deepcopy(spell)]
    if 'discover' in description:
        spell = {
            'class': 'DiscoverSpell',
            'cardSource': {'class': 'CatalogueSource'},
            'cardFilter': {
                'class': 'CardFilter',
                'cardType': 'MINION'
            },
            'spell': {
                'class': 'ReceiveCardSpell'
            }
        }
        spells += [copy.deepcopy(spell)]
    if 'equip' in description:
        spell = {
            'class': 'EquipWeaponSpell',
            'card': 'weapon_fiery_war_axe'
        }
        spells += [copy.deepcopy(spell)]
    if 'destroy' in description:
        spell = {
            'class': 'DestroySpell'
        }
        spells += [copy.deepcopy(spell)]
    if 'transform' in description:
        spell = {
            'class': 'TransformToRandomMinionSpell',
            'cardSource': {
                'class': 'CatalogueSource'
            },
            'cardFilter': {
                'class': 'CardFilter',
                'cardType': 'MINION'
            }
        }
        spells += [copy.deepcopy(spell)]
    if 'freeze' in description:
        spell = {
            'class': 'AddAttributeSpell',
            'attribute': 'FROZEN'
        }
        spells += [copy.deepcopy(spell)]
    if 'random' in description:
        spell['randomTarget'] = True
    target_example = None
    if 'friendly' in description or 'your' in description:
        target_example = 'FRIENDLY_'
    elif 'enemy' in description:
        target_example = 'ENEMY_'
    if target_example is not None:
        if 'minion' in description:
            target_example += 'MINIONS'
        elif 'hero' in description:
            target_example += 'HERO'
        else:
            target_example += 'CHARACTERS'
        spell['target'] = target_example
    if len(spells) > 1:
        spell = {
            'class': 'MetaSpell',
            'spells': spells
        }
    condition = {
        'class': 'PlayedLastTurnCondition'
    }
    if 'combo' in description:
        condition = {
            'class': 'ComboCondition'
        }
    if card_type == 'SPELL':
        if 'if' in description:
            spell['condition'] = condition
        if spell['class'] == 'DamageSpell' or spell['class'] == 'DestroySpell':
            card_dict['targetSelection'] = 'ANY'
        elif spell['class'] == 'BuffSpell':
            card_dict['targetSelection'] = 'MINIONS'
        else:
            card_dict['targetSelection'] = 'NONE'
        card_dict['spell'] = spell
    elif card_type is not None and card_attack is not None and card_health is not None:
        if card_type == 'MINION':
            card_dict['baseAttack'] = int(card_attack)
            card_dict['baseHp'] = int(card_health)
        elif card_type == 'WEAPON':
            card_dict['damage'] = int(card_attack)
            card_dict['durability'] = int(card_health)
    if card_type == 'MINION' or card_type == 'WEAPON':
        if 'deathrattle' in description:
            card_dict['deathrattle'] = spell
            attributes['DEATHRATTLES'] = True
        if 'battlecry' in description:
            card_dict['battlecry'] = {
                'targetSelection': 'NONE',
                'spell': spell
            }
            attributes['BATTLECRY'] = True
        if 'inspire' in description:
            card_dict['trigger'] = {
                'eventTrigger': {
                    'class': 'InspireTrigger',
                    'targetPlayer': 'SELF'
                },
                'spell': spell
            }
        if 'start of game' in description:
            card_dict['gameTriggers'] = [
                {
                    'eventTrigger': {
                        'class': 'GameStartTrigger',
                        'targetPlayer': 'SELF'
                    },
                    'spell': spell
                }
            ]
            if 'if' in description:
                card_dict['gameTriggers'][0]['eventTrigger']['spell']['condition'] = condition
        if 'whenever' in description \
                or 'after' in description \
                or 'at the end' in description \
                or 'at the start' in description \
                or 'after' in description \
                or 'each turn' in description:
            if 'you draw' in description:
                trigger_class = 'CardDrawnTrigger'
            elif 'you summon' in description:
                trigger_class = 'AfterMinionSummonedTrigger' if 'after' in description else 'MinionSummonedTrigger'
            elif 'you play a card' in description:
                trigger_class = 'AfterCardPlayedTrigger' if 'after' in description else 'CardPlayedTrigger'
            elif 'you play a minion' in description:
                trigger_class = 'AfterMinionPlayedTrigger' if 'after' in description else 'MinionPlayedTrigger'
            elif 'you cast' in description:
                trigger_class = 'AfterSpellCastedTrigger' if 'after' in description else 'SpellCastedTrigger'
            else:
                trigger_class = 'TurnEndTrigger'

            card_dict['trigger'] = {
                'eventTrigger': {
                    'class': trigger_class,
                    'targetPlayer': 'SELF' if 'you' in description else 'OPPONENT'
                },
                'spell': spell
            }
            if 'if' in description:
                card_dict['trigger']['eventTrigger']['fireCondition'] = condition
        elif 'battlecry' in card_dict and 'if' in description:
            card_dict['battlecry']['condition'] = condition
        if 'taunt' in description:
            attributes['TAUNT'] = True
        if 'stealth' in description:
            attributes['STEALTH'] = True
        if 'charge' in description:
            attributes['CHARGE'] = True
        if 'divine shield' in description:
            attributes['DIVINE_SHIELD'] = True
        if 'windfury' in description:
            attributes['WINDFURY'] = True
        if 'spell damage' in description:
            attributes['SPELL_DAMAGE'] = 1
        if 'rush' in description:
            attributes['RUSH'] = True
        if 'poisonous' in description:
            attributes['POISONOUS'] = True
        if 'lifesteal' in description:
            attributes['LIFESTEAL'] = True
    if 'overload' in description:
        attributes['OVERLOAD'] = 2
    if 'echo' in description:
        attributes['ECHO'] = True
    if 'quest:' in description:
        card_dict['countUntilCast'] = 5
        card_dict['quest'] = {
            'class': 'CardPlayedTrigger',
            'targetPlayer': 'SELF'
        }
        card_dict['spell'] = {
            'class': 'ReceiveCardSpell',
            'card': 'spell_the_coin'
        }
    elif 'secret:' in description:
        card_dict['secret'] = {
            'class': 'CardPlayedTrigger',
            'targetPlayer': 'SELF'
        }
    if hero_class is not None:
        card_dict['heroClass'] = hero_class
    return card_dict

def write_set_stubs(set_id: int, dest_dir: str, hero_class: str):
    basedir = os.path.join(dest_dir, 'set_' + str(set_id))
    try:
        os.makedirs(basedir)
    except:
        pass
    for card in HearthcardsCardDownloader(set_num=set_id):
        out_card = fix_dict(from_hearthcard_to_spellsource(card, hero_class=hero_class))
        write_card(card=out_card,
                   filepath=os.path.join(basedir, name_to_id(
                       out_card['name'],
                       out_card['type']) + '.json'))