# EG11-14 Modny ciuch wyłącznie ze znacznikami

import pickle

from BTCInput import *

class StockItem(object):
    '''
    Towar magazynowy dla sklepu z odzieżą
    '''

    show_instrumentation = False
    
    min_price = 0.5 
    max_price = 500.0 

    max_stock_add = 50

    @staticmethod
    def get_tag_set_from_text(tag_string):
        '''
        Konwertuje listę rozdzielaną przecinkami na zbiór  
        pojedynczych pozycji
        Konwertuje tekst na wersję złożoną z małych liter i obcina 
        każde słowo
        '''
        # Konwertuje ciąg znaków na małe litery
        tag_string = str.lower(tag_string)

        # Tworzy listę wszystkich słów w ciągu
        # rozdzieloną przecinkiem
        tag_list = str.split(tag_string, sep=',')

        # Usuwa wszystkie spacje na początku lub
        # końcu każdego ciągu na liście
        tag_list = map(str.strip,tag_list)

        # zwraca zestaw utworzony na podstawie listy
        return set(tag_list)


    def __init__(self, stock_ref, price, tags):
        if StockItem.show_instrumentation:
            print('** Wywołano StockItem __init__')
        self.stock_ref = stock_ref
        self.__price = price
        self.__stock_level = 0
        self.StockItem_version = 1
        self.tags = tags

    def check_version(self):
        if StockItem.show_instrumentation:
            print('** Wywołano metodę check_version klasy StockItem')
        # To jest wersja 1 - nie trzeba niczego aktualizować 
        pass

    def __str__(self): 
        if StockItem.show_instrumentation:
            print('** Wywołano metodę __str__ klasy StockItem')
        template = '''Numer magazynowy: {0}
Cena: {1}
Stock Level: {2}
Tags: {3}''' 

        return template.format(self.stock_ref, 
                               self.price, self.stock_level, 
                               self.tags) 

    def add_stock(self,count):
        '''
        Dodaje stan magazynowy dla tego produktu. 
        Count zawiera ilość towaru do dodania
        Zgłasza wyjątek, jeśli liczba jest nieprawidłowa
        '''
        if StockItem.show_instrumentation:
            print('** Wywołano metodę add_stock klasy StockItem')
        if count < 0 or count > StockItem.max_stock_add:
            raise Exception('Nieprawidłowa ilość towaru')

        self.__stock_level = self.__stock_level + count

    def sell_stock(self, count):
        '''
        Sprzedaż towaru.
        count zawiera liczbę towaru do sprzedania
        Zgłasza wyjątek, jeśli liczba jest nieprawidłowa
        '''
        if StockItem.show_instrumentation:
            print('** Wywołano metodę sell_stock klasy StockItem')
        if count < 1:
            raise Exception('Nieprawidłowa ilość towaru do sprzedaży')

        if count > self.stock_level:
            raise Exception('W magazynie nie ma wystarczającej ilości towaru')

        self.__stock_level = self.__stock_level - count

    def set_price(self, new_price):
        if StockItem.show_instrumentation:
            print('** Wywołano metodę set_price klasy StockItem')
        if price < StockItem.min_price or price > StockItem.max_price:
            raise Exception('Cena poza zakresem')
        self.__price = new_price

    @property
    def price(self):
        if StockItem.show_instrumentation:
            print('** Wywołano metodę get price klasy StockItem')
        return self.__price

    @property
    def stock_level(self):
        if StockItem.show_instrumentation:
            print('** Wywołano metodę get stock_level klasy StockItem')
        return self.__stock_level


class FashionShop:

    show_instrumentation = False

    def __init__(self):
        if FashionShop.show_instrumentation:
            print('** Wywołano metodę __init__ klasy FashionShop')
        self.__stock_dictionary = {}

    def save(self, filename):
        '''
        Zapisuje dane sklepu w pliku o podanej nazwie
        Dane są przechowywane w postaci binarnej, w pliku .pickle
        Jeśli zapis nie powiedzie się, program zgłosi wyjątki 
        '''
        if FashionShop.show_instrumentation:
            print('** Wywołano metodę save klasy FashionShop')
        with open(filename,'wb') as out_file:
            pickle.dump(self,out_file)

    @staticmethod
    def load(filename):
        '''
        Ładuje dane sklepu z odzieżą z pliku o podanej nazwie
        Dane są przechowywane w postaci binarnej, w pliku .pickle
        Jeśli ładowanie pliku się nie powiedzie, funkcja będzie zgłaszała wyjątki
        '''
        if FashionShop.show_instrumentation:
            print('** Wywołano metodę load klasy FashionShop')
        with open(filename,'rb') as input_file:
            result = pickle.load(input_file)

        # Teraz zaktualizuj wersje załadowanych towarów
        for stock_item in result.__stock_dictionary.values():
            stock_item.check_version()
        return result


    def store_new_stock_item(self, stock_item):
        '''
        Utwórz nowy towar w sklepie z odzieżą
        Towary są poindeksowane wg wartości stock_ref
        Zgłasza wyjątek, jeśli towar już 
        istnieje 
        '''
        if FashionShop.show_instrumentation:
            print('** Wywołano metodę store_new_stock_item klasy FashionShop')
        if stock_item.stock_ref in self.__stock_dictionary:
            raise Exception('Towar już istnieje') 
        self.__stock_dictionary[stock_item.stock_ref] = stock_item


    def find_stock_item(self, stock_ref):
        '''
        Pobiera towar ze słownika
        Zwraca None, jeśli nie ma towaru dla
        tego klucza
        '''
        if FashionShop.show_instrumentation:
            print('** Wywołano metodę find_stock_item klasy FashionShop')
        if stock_ref in self.__stock_dictionary:
            return self.__stock_dictionary[stock_ref]
        else:
            return None

    def __str__(self):
        if FashionShop.show_instrumentation:
            print('** Wywołano metodę __str__ klasy FashionShop')
        stock = map(str,self.__stock_dictionary.values())
        stock_list = '\n'.join(stock)
        template = '''Towary w magazynie

{0}
'''
        return template.format(stock_list)

    def find_matching_with_tags(self, search_tags):
        '''
        Zwraca pozycje magazynowe, które zawierają
        w zbiorze znaczników znaczniki search_tags
        '''
        if FashionShop.show_instrumentation:
            print('** Wywołano metodę find_matching_tags klasy FashionShop', search_tags)

        def match_tags(item):
            '''
            Zwraca True jeśli znaczniki towaru
            zawierają znaczniki poszukiwane
            '''
            return search_tags.issubset(item.tags)

        return filter(match_tags, self.__stock_dictionary.values())



class FashionShopShellApplication:

    def __init__(self, filename):
        '''
        Zarządza danymi sklepu z odzieżą
        Wyświetla komunikat, jeśli ładowanie się nie powiedzie i tworzy nowy sklep
        '''
        FashionShopShellApplication.__filename = filename
        try:
            self.__shop = FashionShop.load(filename)
        except:
            print('Nie załadowano danych sklepu z odzieżą.')
            print('Tworzenie pustego sklepu z odzieżą')
            self.__shop = FashionShop()

    def create_new_stock_item(self):
        '''
        Tworzy nowy towar magazynowy Pobiera szczegóły towaru, 
        tworzy go, a następnie zapisuje w sklepie
        '''

        stock_ref = read_text('Wprowadź numer magazynowy: ')
        price = read_float_ranged(prompt='Wprowadź cenę: ',
                                    min_value=StockItem.min_price,
                                    max_value=StockItem.max_price)
        tag_string = read_text('Wprowadź znaczniki (rozdzielone przecinkami): ')

        tags = StockItem.get_tag_set_from_text(tag_string)

        new_item = StockItem(stock_ref=stock_ref, price=price, tags=tags)

        try:
            self.__shop.store_new_stock_item(new_item)
            print('Towar zapisano')
        except Exception as e:
            print('Nie zapisano towaru')
            print(e)

    def add_stock(self):
        '''
        Dodaje towar do istniejącej pozycji magazynowej
        Najpierw wyszukuje towar, a następnie pobiera 
        liczbę pozycji towaru do dodania
        '''
        print('Dodaj towar')

        item_stock_ref = read_text('Wprowadź numer magazynowy: ')

        item = self.__shop.find_stock_item(item_stock_ref)
        
        if item == None:
            print('Nie znaleziono towaru')
            return

        print(item)

        number_to_add = read_int_ranged('Ilość towaru do dodania (0 aby zrezygnować): ',
                                        0, StockItem.max_stock_add)

        if number_to_add == 0:
            print('Nie dodano towaru')
        else:
            item.add_stock(number_to_add)
            print(item)

    def sell_stock(self):
        '''
        Sprzedaż towaru Najpierw wyszukuje towar, a następnie odczytuje
        liczbę sztuk towaru do sprzedaży.
        Nie pozwala na sprzedaż większej ilości towarów niż ma na stanie
        '''
        print('Sprzedaż towaru')

        item_stock_ref = read_text('Wprowadź numer magazynowy: ')

        item = self.__shop.find_stock_item(item_stock_ref)

        if item == None:
            print('Nie znaleziono towaru')
            return

        print('Sprzedaż')
        print(item)

        if item.stock_level == 0:
            print('Nie ma towarów w magazynie')
            return

        number_sold = read_int_ranged('Ilość towaru do sprzedaży (0 aby zrezygnować): ',
                                      0,
                                      item.stock_level)

        if number_sold == 0:
            print('Zrezygnowano ze sprzedaży')
            return

        item.sell_stock(number_sold)

        print('Sprzedano towar')

    def do_report(self):
        print('Raport magazynowy')
        print(self.__shop)

    def do_tag_filter(self):
        print('Filtrowanie wg znaczników')
        tag_string = read_text('Wprowadź znaczniki (rozdzielone przecinkami): ')
        search_tags = StockItem.get_tag_set_from_text(tag_string)
        items = self.__shop.find_matching_with_tags(search_tags)
        stock = map(str,items)
        stock_list = '\n'.join(stock)
        template = '''Pasujące towary

{0}
'''
        print(stock_list)

    def main_menu(self):

        prompt = '''Sklep Modny Ciuch

1: Utwórz nowy towar magazynowy
2: Dodaj zapas do istniejącej pozycji
3: Sprzedaj towar
4: Raport ze sprzedaży
5: Wyszukaj wg znaczników
6: Wyjście

Wprowadź polecenie: '''

        while(True):
            command = read_int_ranged(prompt, 1, 6)
            if command == 1:
                self.create_new_stock_item()
            elif command == 2:
                self.add_stock()
            elif command == 3:
                self.sell_stock()
            elif command == 4:
                self.do_report()
            elif command == 5:
                self.do_tag_filter()
            elif command == 6:
                self.__shop.save(FashionShopShellApplication.__filename)
                print('Zapisano dane sklepu z odzieżą')
                break

ui = FashionShopShellApplication('simplefashionshop.pickle')
ui.main_menu()

