## CONFIG
# change this to your external ip address for your server
#(needs to be external to allow tor routing)
from config import *




# monkeypatching the things that asyncio needs
import subprocess
subprocess.PIPE = -1  # noqa
subprocess.STDOUT = -2  # noqa
subprocess.DEVNULL = -3  # noqa


import asyncio
import os
os.environ['KIVY_EVENTLOOP'] = 'asyncio'
# loop = asyncio.get_event_loop()
# loop.set_debug(True)

# imports
from kivy.uix.screenmanager import Screen,ScreenManager
from kivymd.app import MDApp
from kivymd.uix.button import MDFillRoundFlatButton, MDIconButton
from kivymd.uix.toolbar import MDToolbar
from kivymd.uix.screen import MDScreen
from kivymd.uix.dialog import MDDialog
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivymd.theming import ThemeManager
from kivy.properties import ObjectProperty,ListProperty
import time,os
from collections import OrderedDict
from functools import partial
from kivy.uix.screenmanager import NoTransition
from kivymd.uix.label import MDLabel
from kivy.uix.widget import Widget
from kivymd.uix.list import OneLineListItem
from kivymd.uix.card import MDCard, MDSeparator
from kivymd.uix.boxlayout import MDBoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.metrics import dp
from kivy.properties import NumericProperty
from kivymd.uix.list import * #MDList, ILeftBody, IRightBody, ThreeLineAvatarListItem, TwoLineAvatarListItem, BaseListItem, ImageLeftWidget
from kivy.uix.image import Image, AsyncImage
import requests,json
from kivy.storage.jsonstore import JsonStore
from kivy.core.window import Window
from kivy.core.text import LabelBase
import shutil,sys
from kivy.uix.image import Image
import sys
sys.path.append("..") # Adds higher directory to python modules path.

from p2p.api import *
from p2p.persona import *

from kivy.event import EventDispatcher
import threading,asyncio,sys

# raise Exception(str(Window.size))
Window.size = WINDOW_SIZE
# Window.fullscreen = True #'auto'

# with open('log.txt','w') as of:
#     of.write('### LOG ###\n')
def rgb(r,g,b,a=1):
    return (r/255,g/255,b/255,a)

class MyLayout(MDBoxLayout):
    scr_mngr = ObjectProperty(None)
    post_id = ObjectProperty()

    @property
    def app(self):
        if not hasattr(self,'_app'):
            from kivy.app import App
            self._app = App.get_running_app()
        return self._app

    def rgb(self,r,g,b,a=1):
        return rgb(r,g,b,a=a)

    def change_screen(self, screen, *args):
        self.scr_mngr.current = screen

    def change_screen_from_uri(self,uri,*args):
        self.app.uri=uri
        screen_name = route(uri)
        self.app.screen = screen_name
        self.app.log(f'routing to {screen_name}')
        self.scr_mngr.current = screen_name
    
    def view_post(self,post_id):
        self.post_id=post_id
        self.change_screen('view')

class ProgressPopup(MDDialog): pass
class MessagePopup(MDDialog): pass
class MyBoxLayout(MDBoxLayout): pass
class MyLabel(MDLabel): pass

class MyToolbar(MDToolbar):
    action_icon_color = ListProperty()

    def update_action_bar(self, action_bar, action_bar_items):
        action_bar.clear_widgets()
        new_width = 0
        for item in action_bar_items:
            new_width += dp(48)
            action_bar.add_widget(
                MDIconButton(
                    icon=item[0],
                    on_release=item[1],
                    opposite_colors=True,
                    text_color=(self.specific_text_color if not self.action_icon_color else self.action_icon_color),
                    theme_text_color="Custom",
                )
            )
        action_bar.width = new_width

    def update_action_bar_text_colors(self, instance, value):
        for child in self.ids["left_actions"].children:
            if not self.action_icon_color:
                child.text_color = self.specific_text_color
            else:
                child.text_color = self.action_icon_color

        for child in self.ids["right_actions"].children:
            if not self.action_icon_color:
                child.text_color = self.specific_text_color
            else:
                child.text_color = self.action_icon_color




def get_tor_proxy_session():
    session = requests.session()
    # Tor uses the 9050 port as the default socks port
    session.proxies = {'http':  'socks5://127.0.0.1:9150',
                       'https': 'socks5://127.0.0.1:9150'}
    return session    

def get_async_tor_proxy_session():
    from requests_futures.sessions import FuturesSession
    session = FuturesSession()
    # Tor uses the 9050 port as the default socks port
    session.proxies = {'http':  'socks5://127.0.0.1:9150',
                       'https': 'socks5://127.0.0.1:9150'}
    return session    


def get_tor_python_session():
    from torpy.http.requests import TorRequests
    with TorRequests() as tor_requests:
        with tor_requests.get_session() as s:
            return s

def draw_background(widget, img_fn='assets/bg.png'):
    from kivy.core.image import Image as CoreImage
    from kivy.graphics import Color, Rectangle 
    widget.canvas.before.clear()
    with widget.canvas.before:
        Color(.4, .4, .4, 1)
        texture = CoreImage(img_fn).texture
        texture.wrap = 'repeat'
        nx = float(widget.width) / texture.width
        ny = float(widget.height) / texture.height
        Rectangle(pos=widget.pos, size=widget.size, texture=texture,
                  tex_coords=(0, 0, nx, 0, nx, ny, 0, ny))








#### LOOPER



def route(uri):
    if not '/' in uri: return None
    prefix=uri.split('/')[1] #,channel,rest = uri.split('/',3)

    mapd = {
        'inbox':'feed',
        'outbox':'feed',
        'login':'login',
    }
    return mapd.get(prefix,None)



# DEFAULT_SCREEN = route(DEFAULT_URI)

class MainApp(MDApp):
    title = 'Komrade'
    logged_in=False
    # store = JsonStore('../p2p/.keys.json')
    # store_global = JsonStore('../p2p/.keys.global.json')
    store = JsonStore('app.json')
    login_expiry = 60 * 60 * 24 * 7  # once a week
    texture = ObjectProperty()
    uri = '/inbox/world'

    # def connect(self):
    #     # connect to kad?   
    #     self.node = p2p.connect()
    def rgb(self,*_): return rgb(*_)

    def change_screen(self, screen, *args):
        self.screen=screen
        self.root.change_screen(screen,*args)

    @property
    def channel(self):
        if not hasattr(self,'uri'): return None
        if self.uri.count('/')<2: return None
        return self.uri.split('/')[2]

    def change_screen_from_uri(self,uri,*args):
        self.uri=uri
        self.log('CHANGING SCREEN',uri,'??')
        return self.root.change_screen_from_uri(uri,*args)

    @property
    def logger(self):
        if not hasattr(self,'_logger'):
            import logging
            handler = logging.StreamHandler()
            formatter = logging.Formatter('[%(asctime)s]\n%(message)s\n')
            handler.setFormatter(formatter)
            self._logger = logger = logging.getLogger('komrade')
            logger.addHandler(handler)
            logger.setLevel(logging.DEBUG)
        return self._logger

    def log(self,*args,**msgs):
        line = ' '.join(str(x) for x in args)
        self.logger.debug(line)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.event_loop_worker = None
        self.loop=asyncio.get_event_loop()
        
        # load json storage
        self.username=''
        self.load_store()
        self.uri=DEFAULT_URI
        
        # connect to API
        self.api = Api(log=self.log)

    @property
    async def node(self):
        return await self.api.node

    def get_username(self):
        if hasattr(self,'username'): return self.username
        self.load_store()
        if hasattr(self,'username'): return self.username
        return ''


    def build(self):
        # bind 
        global app,root
        app = self
        
        # load root
        self.root = root = Builder.load_file('root.kv')
        draw_background(self.root)
        
        # edit logo
        toolbar=root.ids.toolbar
        toolbar.md_bg_color = root.rgb(*COLOR_TOOLBAR)
        toolbar.action_icon_color=root.rgb(*COLOR_ICON)
        logo=toolbar.ids.label_title
        logo.font_name='assets/Strengthen.ttf'
        logo.font_size='58dp'
        logo.pos_hint={'center_y':0.43}
        logo.text_color=root.rgb(*COLOR_LOGO)
        
        self.root.change_screen_from_uri(self.uri if self.uri else DEFAULT_URI)
        
        return self.root




    def load_store(self):
        if not self.store.exists('user'): return
        userd=self.store.get('user')
        if not userd: return

        self.username = userd.get('username','')
    


    async def upload(self,filename,file_id=None):
        self.log('uploading filename:',filename)
        rdata=await self.api.upload(filename,file_id=file_id)
        self.log('upload result:',rdata)
        if rdata is not None:
            rdata['success']='File uploaded'
            return rdata
        return {'error':'Upload failed'}
        
    async def download(self,file_id,output_fn=None):
        self.log('downloading:',file_id)
        file_dat = await self.api.download(file_id)
        self.log('file_dat =',file_dat)
        if not output_fn:
            file_id=file_dat['id']
            file_ext=file_dat['ext']
            output_fn=os.path.join('cache',file_id[:3]+'/'+file_id[3:]+'.'+file_ext)
        
        output_dir=os.path.dirname(output_fn)
        if not os.path.exists(output_dir): os.makedirs(output_dir)

        with open(output_fn,'wb') as of:
            for data_piece in file_dat['parts_data']:
                if data_piece is not None:
                    of.write(data_piece)
    
    async def post(self, content='', file_id=None, file_ext=None, anonymous=False,channel='world'):
        #timestamp=time.time()
        jsond={}
        #jsond['timestamp']=
        if content: jsond['content']=str(content)
        if file_id: jsond['file_id']=str(file_id)
        if file_ext: jsond['file_ext']=str(file_ext)
        if channel and channel[0]=='@': channel=channel[1:]
        self.log(f'''app.post(
            content={content},
            file_id={file_id},
            file_ext={file_ext},
            anonymous={anonymous},
            channel={channel},
            [username={self.username}]'''
        )
        if not anonymous and self.username:
            jsond['author']=self.username
        
        #jsond['channel']=channel
        self.log('posting:',jsond)
        res=await self.api.post(jsond,channel = channel)
        if 'success' in res:
            self.root.change_screen('feed')
            return {'post_id':res['post_id']}
                    

    @property
    def keys(self):
        return self.api.keys
            
    async def get_post(self,post_id):
        return await self.api.get_post(post_id)

    async def get_posts(self,uri=b'/inbox/world'):
        return await self.persona.read_inbox(uri)
        

        # if uri.count('/')<2: raise Exception('not a URI: '+uri)
        # if 'login' in uri:
        #     raise Exception('!!!! '+uri)

        # self.log(f'app.get_posts(uri={uri} -> ...')
        # data = await self.api.get_posts(uri)
        # self.log(f'app.get_posts() got back from api.get_posts() a {type(data)}')

        # newdata=[]
        # for d in data:
        #     # self.log('data d:',d)
        #     if not 'val' in d: continue
        #     newdict = dict(d['val'].items())
        #     newdict['timestamp']=float(d['time'])
        #     newdict['to_name']=d['channel']
        #     newdata.append(newdict)
        
        # # return index
        # return newdata

    async def get_channel_posts(self,channel,prefix='inbox'):
        # am I allowed to?
        if not channel in self.keys:
            self.log('!! tsk tsk dont be nosy')
            return
        return await self.get_posts(uri='/'+os.path.join(prefix,channel))

    async def get_channel_inbox(self,channel):
        return await self.get_channel_posts(channel=channel,prefix='inbox')
    
    async def get_channel_outbox(self,channel):
        return await self.get_channel_posts(channel=channel,prefix='outbox')

    async def get_my_posts(self):
        return await self.persona.read_outbox()



    ### SYNCHRONOUS?
    def app_func(self):
        '''This will run both methods asynchronously and then block until they
        are finished
        '''
        # self.other_task = asyncio.ensure_future(self.waste_time_freely())
        self.other_task = asyncio.ensure_future(self.api.connect_forever())

        async def run_wrapper():
            # we don't actually need to set asyncio as the lib because it is
            # the default, but it doesn't hurt to be explicit
            await self.async_run() #async_lib='asyncio')
            print('App done')
            self.other_task.cancel()

        return asyncio.gather(run_wrapper(), self.other_task)




    def open_dialog(self,msg):
        if not hasattr(self,'dialog') or not self.dialog:
            self.dialog = ProgressPopup()
        
        # raise Exception(self.dialog, msg)
        self.dialog.text=msg
        self.dialog.open()
        #stop

    def open_msg_dialog(self,msg):
        from screens.post.post import MessagePopup,ProgressPopup
        if not hasattr(self,'msg_dialog') or not self.msg_dialog:
            self.msg_dialog = MessagePopup()
        self.msg_dialog.ids.msg_label.text=msg
        self.msg_dialog.open()

    def close_dialog(self):
        if hasattr(self,'dialog'):
            self.dialog.dismiss()

    def close_msg_dialog(self):
        if hasattr(self,'msg_dialog'):
            self.msg_dialog.dismiss()



if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(MainApp().app_func())
    loop.close()




# def main():
#     # start_logger()
#     App = MainApp()
#     App.run()


# if __name__ == '__main__':
#     # loop = asyncio.get_event_loop()
#     # asyncio.set_event_loop(loop)
#     # loop.run_until_complete(main())
#     # loop.close()
#     main()
