"""Module containing the Fetcher Class to get financial
 data of given ticker using the yfinance package."""

from datetime import datetime, timedelta
import yfinance as yf
import pandas as pd

class Fetcher:
    """Class that fetches data about a given ticker

    This class does a few things:
    1. Checks for validity of arguments before instantiating
    2. Pulls data and checks for whether it contains > 1 day.

    Attributes:
        ticker: String containing the inputted ticker name
        start_date: Beginning day of retrieved financial data
        end_date: Final day of retrieved financial data
    """
    def __init__(self, args: dict) -> None:
        fetcher_args = self._check_args_validity(args)
        self._ticker = fetcher_args["ticker"]
        self._start_date = fetcher_args["start_date"]
        self._end_date = fetcher_args["end_date"]

    def _check_args_validity(self, args: dict) -> dict:
        """Checks for the validity of the CLI arguments

        This function checks for the validity of the input arguments
        before initializing the class. Otherwise, it throws an exception

        Args:
            args: dictionary containing the input CLI Arguments

        Returns:
            dictionary of parsed arguments to be used in yfinance
        """
        # Check for possible ticker errors
        ticker = args["ticker"]

        # Datetime automatically checks for datetime argument errors
        start_date = datetime.strptime(args["b"], "%Y%m%d")
        end_date = datetime.strptime(args["e"], "%Y%m%d") \
            if args["e"] is not None else datetime.now()

        # Compensate for yfinance bug, more specifically:
        # API Given Tracks until 1 day before end
        end_date += timedelta(days = 1)

        # Start date cannot be later than current date
        if start_date > datetime.now():
            raise ValueError("Start date cannot be after current time")

        # Start date cannot be later than the ending date
        if start_date > end_date:
            raise ValueError("End Date is earlier than Start Date")

        fetcher_args = {
            "ticker": ticker,
            "start_date": start_date,
            "end_date": end_date
        }
        return fetcher_args

    def fetch_data(self) -> pd.DataFrame:
        """Function that fetches data from yfinance.

        After checking, it checks for the data validity before proceeding.

        Returns:
            Dataframe with the columns representing financial data
            of the given ticker, arranged from earliest to latest date.
        """
        tracker = yf.Ticker(self._ticker)
        try:
            data: pd.DataFrame = tracker.history(
                start = self._start_date,
                end = self._end_date
            )[self._start_date : self._end_date]
            if len(data) == 0:
                raise Exception("No data available for given ticker.")
            if len(data) == 1:
                raise Exception("Only 1 data point seen. Check time period.")
            return data
        # Error can be caused as raw date is converted to seconds (Line 150):
        # https://github.com/ranaroussi/yfinance/blob/main/yfinance/base.py
        # Best solution is to try a date that's more recent, within 50 years
        except OverflowError as err:
            raise ValueError(
                "Start date too distant. Try a start date within 50 years."
                ) from err
        except BaseException as err:
            raise err


from django.urls import path

from . import views

urlpatterns=[
    path('sub/',views.SubjectVW,name='sub'),
    path('trainer/',views.TrainerVW,name='trainer'),
    path('profile/',views.TranierDisplay,name='profile'),
    path('batchvw/',views.BatchVW,name='batchvw'),
    path('bdisplay/',views.BatchDisplay,name='bdisplay'),
    path('trainerupdate/<pk>/',views.TrainerUP,name='trainerupdate'),
    path('Home/',views.Home,name='Home'),
]


class user():
    def logged_in(self):
      print('You have logged in')

class warden(user):
    def __init__(self, name, age, specification, weapon, strength):
      self.name = name
      self.age = age
      self.specification = specification
      self.weapon = weapon
      self.strength = strength

    def attack(self):
      print(f'The warden is attacking with: {self.strength}')

class witch():
    def __init__(self, name, age, specification, weapon, magic_power):
      self.name = name
      self.age = age
      self.specification = specification
      self.weapon = weapon
      self.magic_power = magic_power

    def attack(self):
        print(f'Morrigen is spelling with her {self.magic_power}')

warden_1 = warden('Roman', 22, 'Swordsman', 'Dual daggers', 37 )
print('The grey Warden: ', warden_1.name, '\n He is', warden_1.age, 'years old', '\n He is an experienced ', warden_1.specification, '\n At the moment you are using ', warden_1.weapon, '\n His current strength is ', warden_1.strength)
warden_1.attack()

def warden_attack_checker(warden_1):
  while warden_1.strength >= 70:
      print('The warden is causing 40% of damage')
      break
  else:
      print('The warden is causing 20% of damage')
warden_attack_checker(warden_1)

witch_1 = witch('Morrigan', 25, 'Witch of the Wilds', 'Magic coal stuff', 74)
print('The wardens ally: ', witch_1.name, '\n She is', witch_1.age, 'years old', '\n She is an experienced ', witch_1.specification, '\n At the moment she is using ', witch_1.weapon, '\n her current power of magic is ', witch_1.magic_power)
witch_1.attack()
def witch_spell_checker(witch_1):
    while witch_1.magic_power >= 60:
        print('Morrigan hurts enemies with 40% of damage')
        break
    else:
        print('Morrigan hurts enemies with 20% of damage')
witch_spell_checker(witch_1)

def player_attack(char):           ### Here is polymorhism principle is exercised! We create a new function and assign a ner attribute 'char' for assigning the function which is COMMON for the both CLASSES and then we can run this function for the both classes
    char.attack()
player_attack(warden_1)
player_attack(witch_1)
                               ## However there is another way how to achieve this result
for char in [warden_1, witch_1]:   ##  we get the same result
    char.attack()


from flask import Flask, render_template, request
from tensorflow.keras.layers import Dense, Embedding, Bidirectional, LSTM, Concatenate, Dropout
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras import Input, Model
import gensim
import numpy as np
import BahdanauAttention #모델.py 불러오기
from konlpy.tag import Mecab
import pickle
import tensorflow as tf
import re

lstm_model = BahdanauAttention.BahdanauAttention(64)

app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False #한글 깨짐 현상
wv_model = gensim.models.Word2Vec.load('model/aihub_review_6.model')
mecab = Mecab(dicpath=r"C:\mecab\mecab-ko-dic") #mecab 윈도우에서 설정
tokenizer = pickle.load(open('model/tokenizer.pickle','rb'))

############ 모델 부분
max_len = 100
EMBEDDING_DIM = 100
sequence_input = Input(shape=(max_len,), dtype='int32')

VOCAB_SIZE = len(tokenizer.index_word) + 1
EMBEDDING_DIM = 100
embedding_matrix = np.zeros((VOCAB_SIZE, EMBEDDING_DIM))

# tokenizer에 있는 단어 사전을 순회하면서 word2vec의 100차원 vector를 가져옵니다
for word, idx in tokenizer.word_index.items():
    embedding_vector = wv_model[word] if word in wv_model else None
    if embedding_vector is not None:
        embedding_matrix[idx] = embedding_vector

embedded_sequences = Embedding(VOCAB_SIZE,
                               EMBEDDING_DIM,
                               input_length=max_len,
                               weights=[embedding_matrix],  # weight는 바로 위의 embedding_matrix 대입
                               trainable=False  # embedding layer에 대한 train은 꼭 false로 지정
                               )(sequence_input)

# embedded_sequences = Embedding(vocab_size, 128, input_length=max_len, mask_zero = True)(sequence_input)
lstm = Bidirectional(LSTM(64, dropout=0.5, return_sequences=True))(embedded_sequences)
lstm, forward_h, forward_c, backward_h, backward_c = Bidirectional(
    LSTM(64, dropout=0.5, return_sequences=True, return_state=True))(lstm)

state_h = Concatenate()([forward_h, backward_h])  # 은닉 상태
state_c = Concatenate()([forward_c, backward_c])  # 셀 상태
attention = lstm_model # 가중치 크기 정의
context_vector, attention_weights = attention(lstm, state_h)

dense1 = Dense(20, activation="relu")(context_vector)
dropout = Dropout(0.5)(dense1)
output = Dense(1, activation="sigmoid")(dropout)
model = Model(inputs=sequence_input, outputs=output)
model.load_weights('model/best_model.h5')
stopwords = ['도', '는', '다', '의', '가', '이', '은', '한', '에', '하', '고', '을', '를', '인', '듯', '과', '와', '네', '들', '듯', '지', '임', '게', '만', '게임', '겜', '되', '음', '면']

def sentiment_predict(new_sentence):
  new_sentence = re.sub(r'[^ㄱ-ㅎㅏ-ㅣ가-힣 ]','', new_sentence)
  new_sentence = mecab.morphs(new_sentence) # 토큰화
  new_sentence = [word for word in new_sentence] # 불용어 제거
  encoded = tokenizer.texts_to_sequences([new_sentence]) # 정수 인코딩
  pad_new = pad_sequences(encoded, maxlen = max_len,padding='post') # 패딩
  score = float(model.predict(pad_new)) # 예측
  return round(score, 2)
  # if(score > 0.5):
  #   print("{:.2f}% 확률로 욕설에 가깝습니다.".format(score * 100))
  # else:
  #   print("{:.2f}% 확률로 욕설이 아닙니다.".format((1 - score) * 100))

@app.route('/', methods=['GET','POST'])
def test():
    return render_template('user.html')

@app.route('/post', methods=['GET','POST'])
def post():
    original_test = request.form['test']
    score = sentiment_predict(original_test)

    return render_template('post.html',  score=score)

@app.route('/ajax_model', methods=['GET','POST'])
def ajax_model():
    original_test = request.json['send_data']
    score = sentiment_predict(original_test)
    return str(score*100)

if __name__ == '__main__':
    app.run()


from django.contrib import admin, messages
from .models import Poll, Question, Choice

class ChoiceInline(admin.StackedInline):
    model = Choice
    extra = 0

class QuestionInline(admin.StackedInline):
    model = Question
    readonly_fields = ['question_type']
    extra = 0

class PollAdmin(admin.ModelAdmin):

    inlines = [QuestionInline]
    fieldsets = [
        (None, {'fields': ['name', 'slug', 'status', 'description']}),
        ('DATE INFO', {'fields': [('starting', 'finished')]})
    ]
    prepopulated_fields = {'slug': ('name',)}
    readonly_fields = [
        'starting',
        'finished',
    ]

    def save_model(self, request, obj, form, change):
        """Save Model override for access control of the poll"""
        import datetime

        def get_message(msg, type):
            messages.add_message(
                request,
                type,
                f'{msg}!'
            )

        if not obj.starting and obj.status == 'IN_PROGRESS':
            obj.starting = datetime.datetime.now()
            get_message('Poll has started!', messages.SUCCESS)
            obj.save()

        if obj.starting and not obj.finished and obj.status == 'FINISHED':
            obj.finished = datetime.datetime.now()
            get_message('Poll has finished!', messages.SUCCESS)
            obj.save()

        if not (obj.starting or obj.finished) and obj.status != 'WAITING':
            obj.status = 'WAITING'
            get_message('Woo Wee Woo Waa! Error!', messages.ERROR)
            obj.save()

        if not obj.id:
            obj.save()

class QuestionAdmin(admin.ModelAdmin):

    inlines = [ChoiceInline]

    # def save_model(self, request, obj, form, change):
    # When Admin choose type of the question is text, answer choices are removing
    #    choices = Choice.objects.filter(question=obj)
    #    if obj.question_type == '1' and choices:
    #       choices.delete()
    #   obj.save()

admin.site.register(Poll, PollAdmin)
admin.site.register(Question, QuestionAdmin)
admin.site.register(Choice)


from unittest import TestCase
import os
from yapic_io.connector import io_connector
import numpy as np
from numpy.testing import assert_array_almost_equal, assert_array_equal
from yapic_io import TiffConnector, Dataset, PredictionBatch
import pytest

from tifffile import memmap

base_path = os.path.dirname(__file__)

class TestPredictionBatch(TestCase):

    @pytest.fixture(autouse=True)
    def setup(self, tmpdir):
        self.tmpdir = tmpdir.strpath

    def test_computepos_1(self):
        img_path = os.path.abspath(os.path.join(
            base_path,
            '../test_data/tiffconnector_1/im/6width4height3slices_rgb.tif'))
        label_path = os.path.join(base_path, '/path/to/nowhere')

        c = TiffConnector(img_path, label_path)
        d = Dataset(c)

        size = (1, 1, 1)
        batch_size = 1

        p = PredictionBatch(d, batch_size, size)

        self.assertEqual(len(p._all_tile_positions), 6 * 4 * 3)
        tilepos = [(p[0], tuple(p[1])) for p in p._all_tile_positions]

        self.assertEqual(len(tilepos),
                         len(set(tilepos)))

    def test_computepos_2(self):
        img_path = os.path.abspath(os.path.join(
            base_path,
            '../test_data/tiffconnector_1/im/6width4height3slices_rgb.tif'))
        label_path = os.path.join(base_path, '/path/to/nowhere')

        c = TiffConnector(img_path, label_path)
        d = Dataset(c)

        size = (3, 6, 4)
        batch_size = 1

        p = PredictionBatch(d, batch_size, size)

        val = [(0, (0, 0, 0))]
        for pos, valpos in zip(p._all_tile_positions, val):
            assert_array_equal(pos[1], np.array(valpos[1]))
            self.assertEqual(pos[0], valpos[0])

    def test_computepos_3(self):
        img_path = os.path.abspath(os.path.join(
            base_path,
            '../test_data/tiffconnector_1/im/6width4height3slices_rgb.tif'))
        label_path = os.path.join(base_path, '/path/to/nowhere')

        c = TiffConnector(img_path, label_path)
        d = Dataset(c)

        size = (2, 6, 4)
        batch_size = 1

        p = PredictionBatch(d, batch_size, size)

        val = [(0, (0, 0, 0)), (0, (1, 0, 0))]
        for pos, valpos in zip(p._all_tile_positions, val):
            assert_array_equal(pos[1], np.array(valpos[1]))
            self.assertEqual(pos[0], valpos[0])

    def test_getitem_1(self):
        img_path = os.path.abspath(os.path.join(
            base_path,
            '../test_data/tiffconnector_1/im/6width4height3slices_rgb.tif'))
        label_path = os.path.join(base_path, '/path/to/nowhere')

        c = TiffConnector(img_path, label_path)
        d = Dataset(c)

        size = (1, 6, 4)
        batch_size = 2

        p = PredictionBatch(d, batch_size, size)

        # batch size is 2, so the first 2 tiles go with the first batch
        # (size two), the third tile in in the second batch. the second
        # batch has only size 1 (is smaller than the specified batch size),
        # because it contains the rest.

        self.assertEqual(len(p), 2)
        self.assertEqual(p[0].pixels().shape, (2, 3, 1, 6, 4))
        self.assertEqual(p[1].pixels().shape, (1, 3, 1, 6, 4))

    def test_getitem_2(self):
        img_path = os.path.abspath(os.path.join(
            base_path,
            '../test_data/tiffconnector_1/im/6width4height3slices_rgb.tif'))
        label_path = os.path.join(base_path, '/path/to/nowhere')

        c = TiffConnector(img_path, label_path)
        d = Dataset(c)

        size = (1, 6, 4)
        batch_size = 3

        p = PredictionBatch(d, batch_size, size)
        # batch size is 3, this means all3 tempplates fit in one batch

        self.assertEqual(len(p), 1)
        self.assertEqual(p[0].pixels().shape, (3, 3, 1, 6, 4))

    def test_current_tile_positions(self):
        img_path = os.path.abspath(os.path.join(
            base_path,
            '../test_data/tiffconnector_1/im/6width4height3slices_rgb.tif'))
        label_path = os.path.join(base_path, '/path/to/nowhere')

        c = TiffConnector(img_path, label_path)
        d = Dataset(c)

        size = (1, 6, 4)
        batch_size = 2
        p = PredictionBatch(d, batch_size, size)

        val = [(0, (0, 0, 0)), (0, (1, 0, 0))]
        for pos, valpos in zip(p[0].current_tile_positions, val):
            assert_array_equal(pos[1], np.array(valpos[1]))
            self.assertEqual(pos[0], valpos[0])

        val = [(0, (2, 0, 0))]
        for pos, valpos in zip(p[1].current_tile_positions, val):
            assert_array_equal(pos[1], np.array(valpos[1]))
            self.assertEqual(pos[0], valpos[0])

    def test_put_probmap_data(self):
        img_path = os.path.abspath(os.path.join(
            base_path,
            '../test_data/tiffconnector_1/im/6width4height3slices_rgb.tif'))
        label_path = os.path.join(base_path, '/path/to/nowhere')

        c = TiffConnector(img_path, label_path, savepath=self.tmpdir)
        d = Dataset(c)

        size = (1, 6, 4)
        batch_size = 1

        p = PredictionBatch(d, batch_size, size)

        data = np.ones((1, 2, 1, 6, 4))
        p[0].put_probmap_data(data)
        p[1].put_probmap_data(data)
        p[2].put_probmap_data(data)

    def test_put_probmap_data_2(self):
        img_path = os.path.abspath(os.path.join(
            base_path,
            '../test_data/tiffconnector_1/im/6width4height3slices_rgb.tif'))
        label_path = os.path.join(base_path, '/path/to/nowhere')

        c = TiffConnector(img_path, label_path, savepath=self.tmpdir)
        d = Dataset(c)

        size = (1, 2, 2)
        batch_size = 1

        p = PredictionBatch(d, batch_size, size)

        pixel_val = 0
        for mb in p:
            pixel_val += 10
            data = np.ones((1, 2, 1, 2, 2)) * pixel_val
            mb.put_probmap_data(data)

        pixelmap = memmap(os.path.join(self.tmpdir,
                          '6width4height3slices_rgb_class_1.tif'))

        # zslice 0
        val_0 = np.array([[10., 10., 30., 30., 50., 50.],
                          [10., 10., 30., 30., 50., 50.],
                          [20., 20., 40., 40., 60., 60.],
                          [20., 20., 40., 40., 60., 60.]])
        assert_array_almost_equal(pixelmap[0, :, :, 0], val_0)

        # zslice 1
        val_1 = np.array([[70.,  70.,  90.,  90., 110., 110.],
                          [70.,  70.,  90.,  90., 110., 110.],
                          [80.,  80., 100., 100., 120., 120.],
                          [80.,  80., 100., 100., 120., 120.]])
        assert_array_almost_equal(pixelmap[1, :, :, 0], val_1)

        # zslice 2
        val_2 = np.array([[130., 130., 150., 150., 170., 170.],
                          [130., 130., 150., 150., 170., 170.],
                          [140., 140., 160., 160., 180., 180.],
                          [140., 140., 160., 160., 180., 180.]])
        assert_array_almost_equal(pixelmap[2, :, :, 0], val_2)

    def test_put_probmap_data_3(self):
        img_path = os.path.abspath(os.path.join(
            base_path, '../test_data/tiffconnector_1/im/*'))
        label_path = os.path.abspath(os.path.join(
            base_path, '../test_data/tiffconnector_1/labels/*'))

        c = TiffConnector(img_path, label_path, savepath=self.tmpdir)
        d = Dataset(c)

        size = (1, 3, 4)
        batch_size = 2

        p = PredictionBatch(d, batch_size, size)

        data = np.ones((2, 3, 1, 3, 4))
        p[0].put_probmap_data(data)

        data = np.ones((2, 3, 1, 3, 4))
        p[1].put_probmap_data(data)

        data = np.ones((2, 3, 1, 3, 4))
        p[2].put_probmap_data(data)

    def test_put_probmap_data_when_no_labels_available(self):

        img_path = os.path.abspath(os.path.join(
            base_path, '../test_data/tiffconnector_1/im/*'))

        c = io_connector(img_path, '', savepath=self.tmpdir)
        d = Dataset(c)

        size = (1, 3, 4)
        batch_size = 2

        p = PredictionBatch(d, batch_size, size)

        data = np.ones((2, 2, 1, 3, 4))
        p[0].put_probmap_data(data)

        data = np.ones((2, 2, 1, 3, 4))
        p[1].put_probmap_data(data)

        data = np.ones((2, 2, 1, 3, 4))
        p[2].put_probmap_data(data)

        val = ['40width26height3slices_rgb_class_1.tif',
               '40width26height3slices_rgb_class_2.tif']
        self.assertEqual(sorted(os.listdir(self.tmpdir)), val)

    def test_put_probmap_data_multichannel_label(self):
        img_path = os.path.abspath(os.path.join(
            base_path, '../test_data/tiffconnector_1/im/*'))
        label_path = os.path.abspath(os.path.join(
            base_path, '../test_data/tiffconnector_1/labels_multichannel/*'))

        c = TiffConnector(img_path, label_path, savepath=self.tmpdir)
        d = Dataset(c)

        original_labels = c.original_label_values_for_all_images()
        res = c.calc_label_values_mapping(original_labels)

        d = Dataset(c)

        size = (1, 3, 4)
        batch_size = 1

        p = PredictionBatch(d, batch_size, size)

        data = np.ones((1, 6, 1, 3, 4))
        p[0].put_probmap_data(data)

    def test_prediction_loop(self):
        # mock classification function
        def classify(pixels, value):
            return np.ones(pixels.shape) * value

        # define data locations
        pixel_image_dir = os.path.join(
            base_path, '../test_data/tiffconnector_1/im/*.tif')
        label_image_dir = os.path.join(
            base_path, '../test_data/tiffconnector_1/labels/*.tif')

        tile_size = (1, 5, 4)  # size of network output layer in zxy
        padding = (0, 0, 0)  # padding of network input layer in zxy,
        # in respect to output layer

        # Make training_batch mb and prediction interface p with
        # TiffConnector binding.
        c = TiffConnector(pixel_image_dir,
                          label_image_dir, savepath=self.tmpdir)
        p = PredictionBatch(Dataset(c), 2, tile_size, padding_zxy=padding)

        self.assertEqual(len(p), 255)
        self.assertEqual(p.labels, {1, 2, 3})

        # classify the whole bound dataset
        for counter, item in enumerate(p):
            pixels = item.pixels()  # input for classifier
            mock_classifier_result = classify(pixels, counter)
            # pass classifier results for each class to data source
            item.put_probmap_data(mock_classifier_result)

    def test_pixel_dimensions(self):

        img_path = os.path.abspath(os.path.join(
            base_path, '../test_data/tiffconnector_1/im/*'))
        c = io_connector(img_path, '', savepath=self.tmpdir)
        d = Dataset(c)

        size = (1, 5, 4)
        batch_size = 2

        p = PredictionBatch(d, batch_size, size)[0]

        print(p.pixels().shape)
        self.assertEqual((2, 3, 1, 5, 4), p.pixels().shape)

        p.set_pixel_dimension_order('bzxyc')
        self.assertEqual((2, 1, 5, 4, 3), p.pixels().shape)


# preprocessing data
data = open("input/day14.txt").read().splitlines()
data = [line.split("->") for line in data]
data = [[x.strip().split(",") for x in line] for line in data]
data = [[[int(num) for num in pair] for pair in line] for line in data]
balance_x = 450
data = [[[pair[0] - balance_x, pair[1]] for pair in line] for line in data]
# %% With small sample
# data = open("input/day14_small.txt").read().splitlines()
# data = [line.split("->") for line in data]
# data = [[x.strip().split(",") for x in line] for line in data]
# data = [[[int(num) for num in pair] for pair in line] for line in data]
# balance_x = 490
# data = [[[pair[0] - balance_x, pair[1]] for pair in line] for line in data]
# %%

# flatten data
only_x = [item for line in data for pair in line for item in pair[:1]]
only_y = [item for line in data for pair in line for item in pair[1:]]
min_x, max_x = min(only_x), max(only_x)
min_y, max_y = min(only_y), max(only_y)
# %%
def create_wall_of_rock(ins):
    global table
    for index in range(len(ins)):
        if index == len(ins) - 1:
            break
        first = ins[index]
        next_ = ins[index + 1]
        if first[1] == next_[1]:
            # all point between with the same y
            for x in range(min(first[0], next_[0]), max(first[0], next_[0]) + 1):
                table[first[1]][x] = 1
        if first[0] == next_[0]:
            for y in range(min(first[1], next_[1]), max(first[1], next_[1]) + 1):
                table[y][first[0]] = 1

# %%  part 1
table = [[0 for x in range(max_x + 2)] for y in range(max_y + 2)]
for instruction in data:
    create_wall_of_rock(instruction)
# %%
from copy import deepcopy

drop_point = [500 - balance_x, 0]
void_deep = max_y + 1
count_sand_before_go_void = 0

def model_sand_drop():
    global table, drop_point, void_deep, count_sand_before_go_void
    current = deepcopy(drop_point)
    while True:
        x_current = current[0]
        y_current = current[1]
        # if below still empty, continue drop down
        if y_current >= void_deep:
            break
        if table[y_current + 1][x_current] == 0:
            current[1] += 1
        else:
            # if below is wall, check the left first
            if table[y_current + 1][x_current - 1] == 0:
                current[0] -= 1
                current[1] += 1
            else:
                # if the left is wall, check the right
                if table[y_current + 1][x_current + 1] == 0:
                    current[0] += 1
                    current[1] += 1
                else:
                    # if both left and right are wall, stop
                    count_sand_before_go_void += 1
                    break
    # return if this sand go to the void
    table[current[1]][current[0]] = 2
    # print(current)
    return current[1] >= void_deep

# %%
while True:
    if model_sand_drop():
        break
# %% part 2
from copy import deepcopy

drop_point = [500 - balance_x, 0]
real_void_deep = max_y + 2

def model_sand_drop2():
    global table, drop_point, void_deep
    current = deepcopy(drop_point)
    while True:
        x_current = current[0]
        y_current = current[1]
        # if below still empty, continue drop down
        if table[drop_point[1]][drop_point[0]] == 2:
            break
        if current[1] == real_void_deep - 1:
            break
        if table[y_current + 1][x_current] == 0:
            current[1] += 1
        else:
            # if below is wall, check the left first
            if table[y_current + 1][x_current - 1] == 0:
                current[0] -= 1
                current[1] += 1
            else:
                # if the left is wall, check the right
                if table[y_current + 1][x_current + 1] == 0:
                    current[0] += 1
                    current[1] += 1
                else:
                    # if both left and right are wall, stop
                    break
    table[current[1]][current[0]] = 2

# %% prepare table, the bottom maximum length is 2x of the (max_y + 1)
table = [[0 for x in range(2*(max_y + 2) + 1)] for y in range(max_y + 2)]
for instruction in data:
    create_wall_of_rock(instruction)
#%%
while True:
    if table[drop_point[1]][drop_point[0]] == 2:
        break
    model_sand_drop2()
#%%
sum([line.count(2) for line in table])
# %%
from pandas import DataFrame
import seaborn as sns

df = DataFrame(table)
sns.heatmap(df)
# %%


'''
Exercises of the book "Think python"
14.12.2 Exercise:
'''

# If you download my solution to Exercise 2 from
# http://thinkpython2.com/code/anagram_sets.py, you’ll see that it
# creates a dictionary that maps from a sorted string of letters
# to the list of words that can be spelled with those letters.
# For example, 'opst' maps to
# the list ['opts', 'post', 'pots', 'spot', 'stop', 'tops'].
#
# Write a module that imports anagram_sets and provides two new
# functions: store_anagrams should store the anagram dictionary
# in a “shelf”; read_anagrams should look up a word and return
# a list of its anagrams.
# Solution: http://thinkpython2.com/code/anagram_db.py.

import shelve
import os
from anagrams import all_anagrams

def store_anagrams(anagrams_dict):
    """Stores anagrams in a shelf"""

    # Store the anagrams dict in 'shelf'
    with shelve.open('saved_anagrams') as db:
        db['anagrams_dict'] = anagrams_dict

def read_anagrams(word):
    """Looks for the list of anagrams for the word"""

    # Read anagrams dictionary
    with shelve.open('saved_anagrams') as db:
        saved_anagrams = db.get('anagrams_dict')

    # Get the list of anagrams for the word
    key = ''.join(sorted(word))
    anagrams_list = saved_anagrams.get(key, "No such word in the dict")
    anagrams_list.remove(word)

    return anagrams_list

# Test program
if __name__ == "__main__":
    path = os.path.sep.join(["chapter14", "words.txt"])

    anagrams = all_anagrams(path)
    store_anagrams(anagrams)
    result = read_anagrams("pots")

    print(result)