import logging
logging.basicConfig()
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
logger.info("Loaded " + __name__)

import cv2
import numpy as np
import urllib
import base64
import time
import threading
import traceback
import datetime
try:
    import jetson.utils
except:
    pass

from .videoManager import VideoManager
from .motionDetector import motionDetector

# RTSP based camera capture
class cap_rtsp():

    def __init__(self, config,behaviour_config={}):
        
        self.config = config
        logger.info("URL : " + str(self.config["url"]))
        username = self.config.get('username', None)
        password = self.config.get('password', None)
        # Create the rtsp url
        if username and password and(self.config['url'].find('@')==-1):
            url=self.config['url'][0:7]+username+':'+password+'@'+self.config['url'][7:]
            self.config['url']=url
        # Get the params
        params = self.config.get('params',{})
        self.config['params']=params
        # Get the opencv backend
        self.backend = self.config['params'].get('backend', cv2.CAP_FFMPEG)
        # Create camera capture object
        self.video = cv2.VideoCapture(self.config['url'],self.backend)
        self.source_FPS = self.config['params'].get('source_fps', int(self.video.get(cv2.CAP_PROP_FPS)))
        if self.source_FPS> 100 or self.source_FPS==0:
            self.source_FPS=30
            
        print("fps of camera-{}".format(self.source_FPS))
        self.FPS = self.config['params'].get('fps', 5)
        self.MAXSKIP=500
        
        self.SKIP = int(self.source_FPS/self.FPS) if self.source_FPS and self.FPS else 1
        self.lastFeedTime=None
        
        behaviour_params=behaviour_config.get("params",{})
        videoConfig=behaviour_params.get("videoConfig",{})
        motionConfig=behaviour_params.get("motionConfig",{})
        behaviour_id=behaviour_config.get('_id','test-behaviour')
        # This check shall refresh camera buffer if camera is not read
        self.enableCheckBuffer=behaviour_params.get('checkBufferThread', False)
        
        # If read method is camera, image is read directly by rtsp url
        # If video: Image is read via videos saved through manager
        self.readMethod = behaviour_params.get('readMethod', 'camera')
        
        # If motion enabled -only frames having motion are saved
        self.motionEnabled=behaviour_params.get("motionEnabled",False)
        self.manager=None
        self.lock=False
        self.failed_attempts =0
        # Initialize the video manager, which will manage writing/reading of videos
        if self.readMethod=='video':
            logger.info("video Mangaer working with config-{}".format(videoConfig))
            filename=videoConfig.get("filename","vid.avi")
            fps=videoConfig.get("fps",30)
            frameSize=videoConfig.get("frameSize",(1280,720))
            base_dir=videoConfig.get("base_dir","/LFS/videos/{}".format(self.config['_id']))
            process_dir=videoConfig.get("base_dir","/LFS/videos/{}".format(behaviour_id))
            max_sizeMB=videoConfig.get("max_sizeMB",500)
            max_seconds=videoConfig.get("max_seconds",100)
            max_period=videoConfig.get("max_period",60)
            cleanupDays=videoConfig.get("cleanupDays",0.5)
            
            if self.motionEnabled:
                self.motionDetector=motionDetector(motionConfig)
                self.lasWriteTime=time.time()
                self.maxWriteWaitTime=max_period
                
            self.manager = VideoManager(
                                        filename=filename,
                                        fourcc = 'XVID',
                                        fps = fps,
                                        frameSize = frameSize,
                                        base_dir = base_dir,
                                        process_dir=process_dir,
                                        max_sizeMB = max_sizeMB,
                                        max_seconds= max_seconds,
                                        max_period= max_period,
                                        cleanupDays = cleanupDays
                                        )
            self.stopReader=False
            
            # If this parameter is set to false, only videos will be read not
            # written - usage when multiple behaviuors are reading from same rtsp
            # All behaviours need not do the same, they can just read
            self.writeVideoAllowed = behaviour_params.get("writeVideoAllowed",True)
            if self.writeVideoAllowed:
                self.writeThread=threading.Thread(target=self.read_write)
                self.writeThread.start()
                
        if self.enableCheckBuffer:
            self.checkBufferInterval=behaviour_params.get('checkBufferInterval', 3)
            self.initiate_check_buffer_thread()
    
    # Gets the process image actual time
    # applicable when processed through video buffer  
    def get_process_time(self):
        if self.manager:
            return self.manager.processTime
        else:
            return datetime.datetime.now()
        
    # Manger reads frame from rtsp and dumps to video file
    def read_write(self):
        try:
            while self.stopReader==False:
                try:
                    img=self.run()
                except Exception as e:
                    logger.info("error occured in reading-{}".format(str(e)))
                    img=None
                if img is not None:
                    try:
                        if self.motionEnabled:
                            motion=self.motionDetector.detect(img)
                            if motion:
                                logger.info("motion detected,writing to video file")
                                self.manager.write(img)
                                self.lasWriteTime=time.time()
                            if (time.time()-self.lasWriteTime) > self.maxWriteWaitTime:
                                logger.info("no motion but still writing frame to release video slot")
                                self.manager.write(img)
                                self.lasWriteTime=time.time()         
                        else:    
                            self.manager.write(img)
                    except Exception as e:
                        logger.info("error occured in writing-{}".format(str(e)))
                else:
                    logger.info("reinitializing camera-{}".format(self.config['name']))
                    self.reinitialize() 
                           
                time.sleep(0.01)
        except Exception as e:
            logger.error("Error occurred grabbing frame-{}".format(str(e)))
            logger.error(traceback.format_exc())
            try:
                self.reinitialize() 
            except Exception as e:
                logger.error("Failed to renitialize also")
                logger.error(traceback.format_exc())
           
    def stopReadWriteThread(self):
        try:
            self.stopReader=True
            self.read_write.join()
        except Exception as e:
            logger.error("unable to stop readwrite thread of videomanager")
                    
              
    # Thread for managing camera buffer   
    def check_maintain_buffer(self):
        while(self.checkBuffer):
            try:
                if not self.lock:
                    #self.holdLock("buffercheck")
                    if self.lastFeedTime:
                        if(time.time()-self.lastFeedTime>self.checkBufferInterval):
                            logger.info("maintaing camera buffer-{}".format(self.config['name']))
                            ret,frame=self.read()
                    #self.clearLock("buffercheck")
                else:
                    logger.info("lock held..skipping buffer check-{}".format(self.config['name']))
                time.sleep(self.checkBufferInterval-1)
            except Exception as e:
                logger.info("some exception occurred in buffercheck-{}".format(str(e)))
                logger.info(traceback.format_exc())
                
                        
    def initiate_check_buffer_thread(self):
        self.checkBuffer = True
        self.checkBufferThread=threading.Thread(target=self.check_maintain_buffer)
        self.checkBufferThread.start()
        
    def set(self,attribute,value):
        self.video.set(attribute,value)
    
    def holdLock(self,obj):
        #logger.info("lock held by -{}-{}".format(obj,self.config['name']))
        self.lock=True
        
    def clearLock(self,obj):
        #logger.info("lock cleared by -{}-{}".format(obj,self.config['name']))
        self.lock=False

    # Main read method - If video manger is present, image is read via manager
    # Else image is directly read through rtsp url
    def read(self):
        
        # If reading is managed by videos
        if self.manager is not None:
            if self.writeVideoAllowed:
                if not self.writeThread.is_alive():
                    logger.error("video writer thread seems to be dead, trying to close application")
                    raise RuntimeError('video writer thread seems crashed')
            return self.manager.read()
        
        # If reading is manged directly by camera
        while(self.lock==True):
            logger.info("waiting for lock to clear-{}".format(self.config['name']))
            time.sleep(0.1)
        self.holdLock("reader")
        if (self.video.isOpened()):     
            frame= self.run()
            self.clearLock("reader")
            
            if frame is None:
                return 0,None
            else:
                return 1,frame
        else:
            self.clearLock("reader")
            logger.info('camera capture object not opened for camera {} ,url{}'.format(self.config['name'],self.config['url']))
            return 0,None

    # Reinitializes opencv camera capture object
    def reinitialize(self):
        self.lastFeedTime=None
        while(self.lock==True):
            logger.info("waiting for lock to clear")
            time.sleep(0.1)
        logger.info("Camera reinitialize called-{}".format(self.config['name']))
        self.holdLock("reinitializer")
        if self.enableCheckBuffer:
            self.checkBuffer = False
            self.checkBufferThread.join()
        self.video.release()
        self.video=None
        self.video = cv2.VideoCapture(self.config['url'],self.backend)
        if self.enableCheckBuffer:
            self.initiate_check_buffer_thread()
        self.clearLock("reinitializer")
        

    def clear(self):
        self.config = None
        self.source_FPS = None
        self.FPS = None
        self.SKIP = None
        self.video.release()
        
    def release(self):
        self.clear()

    # This method automatically asseses the camera read cycle
    # and calculates the buffer size to skip, trying to return the 
    # latest image
    def run(self):
    
        if self.lastFeedTime is not None:
            timeTaken = time.time()-self.lastFeedTime  
            fps=1.0/timeTaken
            self.SKIP = int(self.source_FPS/fps)+1
            self.SKIP = min(self.SKIP,self.MAXSKIP)
            #logger.info("fps obtained-{} ,skipping frames-{}".format(fps,self.SKIP))
            self.lastFeedTime=time.time()
        else:
            self.lastFeedTime=time.time()

        start=time.time()
        for skip in range(self.SKIP)[::-1]:
            Grab_Success = False 
            try:
                Grab_Success = self.video.grab()
            except Exception as e:
                logger.info("Exception in Camera grabbing frame...")
                Grab_Success  =False
            
            if Grab_Success:
                if not skip:
                    ret, frame = self.video.retrieve()
                    if ret==1 :
                        #logger.info("time taken to grab frame-{}".format(time.time()-start))
                        self.lastFeedTime=time.time()
                        return frame            
        return None

# This class implememts jetson specific camera capture object
# optimized by nvidia for fetching camera streams        
class cap_jetson():
    def __init__(self,config):
        self.config = config
        logger.info("URL : " + str(self.config["url"]))
        username = self.config.get('username', None)
        password = self.config.get('password', None)
        if username and password and(self.config['url'].find('@')==-1):
            url=self.config['url'][0:7]+username+':'+password+'@'+self.config['url'][7:]
            self.config['url']=url
        self.video=jetson.utils.videoSource(self.config['url'])
    def read(self):
        img = jetson.utils.cudaToNumpy(self.video.Capture())
        if img is not None:
            img=cv2.cvtColor(img,cv2.COLOR_RGB2BGR)
            return 1,img
        else:
            return 0,None
    def reinitialize(self):
        self.video=None
        self.video=jetson.utils.videoSource(self.config['url'])
        
## http based camera capture for http streams from camera
class cap_http():

    def __init__(self,config):
        self.config = config
        url = self.config['url']
        logger.info("URL : " + url)

        username = self.config.get('username', '')
        password = self.config.get('password', '')

        userpass = '%s:%s' % (username, password)
        base64string=base64.b64encode(userpass.encode()).decode()
        request = urllib.request.Request(url)
        request.add_header("Authorization", "Basic %s" % base64string)


        try:   
            self.stream = urllib.request.urlopen(request)
        except Exception as e:
            logger.info("Cmaera Stream object was not created")
            logger.info(e)

        self.generator = self.gen()

    def read(self):
        return next(self.generator)    

    def gen(self):
        byytes = bytes()
        fail =0
        while True:
            try:
                byytes += self.stream.read(1024)
                a = byytes.find(b'\xff\xd8')
                b = byytes.find(b'\xff\xd9')
                if a != -1 and b != -1:
                    jpg = byytes[a:b+2]
                    byytes = byytes[b+2:]
                    image = cv2.imdecode(np.fromstring(jpg, dtype=np.uint8),cv2.IMREAD_COLOR)
                    fail = 0
                    yield 1,image
            except Exception as e:
                logger.info(e)
                yield 0, None
    def reinitialize(self):
        pass

    def set(self,id,value):
        pass   

    def isOpened(self):
        return True


if __name__ == '__main__':
    
    cam_config={
        "name":"testcamera",
        "url":0,
        "_id":"1234",
        "params":{"readMethod":"video","motionEnabled":True}
        }
    vid_config = {
        "base_dir":"/Users/saurabh_veda/workspace_kumar/new_test"
    }
    
    cap = cap_rtsp(cam_config,vid_config)
    
    while True:
        ret,image=cap.read()
        if ret:
            cv2.imshow("vid",image)
            cv2.waitKey(1)
    #cap=cv2.VideoCapture(0)
    
    # while True:
    #     ret,img=cap.read()
    #     manager.write(img)
    