import time
from wflow_sdk.core.dataset import WFlowDataset
from torch.utils.data import DataLoader
from torchvision import transforms
from torch.nn.utils.rnn import pad_sequence
import random
import json
import torch
import sys
import librosa
sys.path.append('../')
from dataset.tokenization import BertTokenizer
from utils import _build_vid_pos_ids
config = json.load(open('./conf/uvat.json','r'))
import utils
config = utils.Config(config)
import numpy as np

with open('./data/quert_data_train.json')as ff:
    for ll in ff:
        query_nid = json.loads(ll.strip())
num=0
transform = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
def pad_tensors_img(tensors, lens=None, pad=0):
    """B x [T, ...]"""
    if len(tensors[0]) == 0:
        return torch.zeros(0, dtype=tensors[0].dtype)
    if lens is None:
        lens = [t.shape[0] for t in tensors]
    #print(len(tensors),tensors[0].shape)
    max_len = max(lens)
    bs = len(tensors)
    h, w = tensors[0].shape[2:] #tensors[0].size(-1)
    dtype = tensors[0].dtype
    output = torch.zeros(bs, max_len, 3, h, w, dtype=dtype)

    if pad:
        output.data.fill_(pad)
    for i, (t, l) in enumerate(zip(tensors, lens)):
        # print('t, l: ', t.shape, l)
        output.data[i, :l, ..., ..., ...] = t.data
    return output
def pad_tensors_audio(tensors, lens=None, pad=0):
    """B x [T, ...]"""
    if lens is None:
        lens = [t.shape[0] for t in tensors]
    if len(tensors[0]) == 0:
        return torch.zeros(0, dtype=tensors[0].dtype)
    max_len = max(lens)
    c, _ = tensors[0].shape
    bs = len(tensors)
    #h, w = tensors[0].shape[2:] #tensors[0].size(-1)
    dtype = tensors[0].dtype
    output = torch.zeros(bs, c, max_len, dtype=dtype)

    if pad:
        output.data.fill_(pad)
    for i, (t, l) in enumerate(zip(tensors, lens)):
        # print('t, l: ', t.shape, l)
        output.data[i, :, :l] = t.data
    return output#.unsqueeze(1)

spatial_patch_size = config.spatial_patch_size
temporal_patch_size = config.temporal_patch_size
audio_temporal_patch_size = config.audio_temporal_patch_size
max_num_image = config.max_image
def fn(d):
    print(d[0].keys())
    #print(d[0]["DATA_VIDEO_FRAMES"][0])
    #print(type(d[0]["DATA_VIDEO_FRAMES"]))
    #print(len(d[0]["DATA_VIDEO_FRAMES"]))
    #print(d[0]["DATA_VIDEO_FRAMES"])
    #print(d[0]["DATA_VIDEO_FRAMES"][0].shape)
    #print(len(d[0]["DATA_VIDEO_FRAMES"]))
    return d
with open('./data/quert_data_train.json')as ff:
    for ll in ff:
        query_nid = json.loads(ll.strip())
tokener = BertTokenizer.from_pretrained('./dataset')
#dict_keys(['DATA_NID', 'DATA_AUDIO', 'DATA_TAG', 'DATA_CATEGORY', 'DATA_SUB_CATEGORY', 'DATA_VIDEO_FRAMES'])
def get_query_label_mask(nid):

    if nid in query_nid:
        if isinstance(query_nid[nid], str):
            query = eval(query_nid[nid])
        else:
            quert = query_nid[nid]
        #print('nid query:',query)
        if random.random() >0.48:
            #print('random smple:',random.sample(query, 1))
            #print('type:',type(query_nid[nid]))
            query = random.sample(query, 1)[0]
            query_data_label = 1
            #print('非随机query:',query)
        else:
            random_key = random.sample(query_nid.keys(), 1)[0]
            query = random.sample(eval(query_nid[random_key]),1)[0]
            #query = random.sample(random.sample(query_nid.keys(), 1)[0], 1)[0]
            #print('query 随机1',query)
            query_data_label = 0
    else:
        random_key = random.sample(query_nid.keys(), 1)[0]
        query = random.sample(eval(query_nid[random_key]),1)[0]
        #print('query 随机2',query)
        query_data_label = 0
    query = tokener.tokenize(query)
    query = query + ['[SEP]']
    query = tokener.convert_tokens_to_ids(query)
    query_len = min(config.max_text_len, len(query))

    return torch.tensor(query[:query_len]), torch.tensor(query_data_label), torch.ones(query_len, dtype=torch.long)

def get_mask_title(title):
    #print(title)
    if len(title)<1:
        title = "视频无标题"
        text = tokener.tokenize(title)
        text_id = tokener.convert_tokens_to_ids(text)
        len_title = min(config.max_text_len, len(text_id))
        return torch.tensor(text_id[:len_title]), torch.ones(len_title, dtype=torch.long)

    text = tokener.tokenize(title)
    for i in range(len(text)):
        if random.random() < 0.5:
            text[i] = '[MASK]'
    text_id = tokener.convert_tokens_to_ids(text)
    len_title = min(config.max_text_len, len(text_id))
    return torch.tensor(text_id[:len_title]), torch.ones(len_title, dtype=torch.long)
def get_mask_audio(wav):
    try:
        s = time.time()
        wav = wav[: config.max_audio]
        #wav = wav[: 13*100]
        s2 = time.time()
        wav = wav/(max(wav)+1e-7)
        s3 = time.time()
        #audio_mfcc = wav.reshape((13, -1))
        #audio_mfcc = np.ones((13, 100))
        #print(audio_mfcc.shape)
        audio_mfcc = librosa.feature.mfcc(wav, sr=16000, n_mfcc = 13)
        audio_mfcc = torch.tensor(audio_mfcc).to(torch.float32)
        #audio_mfcc = torch.tensor(audio_mfcc)
        #print("dtype", audio_mfcc.dtype)
        #audio_mfcc = torch.tensor(audio_mfcc, dtype=torch.float32)
        #print(audio_mfcc.shape)
        e = time.time()
        #print(e - s, "e - s audio", s2 - s, s3 - s2, e - s3, audio_mfcc.dtype, audio_mfcc.shape)
        return audio_mfcc, audio_mfcc.shape[-1]
    except Exception as e:
        print(e)
        #print(wav)
transform = transforms.Compose([
    transforms.ToPILImage(),
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
def get_mask_video(video):
    imgs = []
    for img in video:
        #print(type(img))
        #print(img.shape)
        imgs.append(transform(img))
    if len(imgs) >= config.temporal_patch_size:
        num = int(len(imgs) // config.temporal_patch_size * config.temporal_patch_size)
        imgs = imgs[:num]
    else:
        for i in range(config.temporal_patch_size-len(imgs)):
            imgs.append(torch.zeros([3,224,224]))
            num = config.temporal_patch_size
    imgs = torch.stack(imgs)
    return imgs, num

import time
from concurrent.futures import ThreadPoolExecutor
executor = ThreadPoolExecutor(max_workers=8)
executor2 = ThreadPoolExecutor(max_workers=8)
executor3 = ThreadPoolExecutor(max_workers=8)
executor4 = ThreadPoolExecutor(max_workers=8)

import gc
def vqa_collate_no(inputs):
    #return inputs
    #del inputs
    gc.collect()
    return torch.ones((10,10))

def collate0(inputs):
    s = time.time()
    #print("start vqa_collate")
    #(input_ids, img_feats, img_pos_feats, attn_masks, targets
    # ) = map(list, unzip(inputs))
    data_audios = []
    data_tags = []
    data_nids = []
    data_video_frames = []

    input_len = len(inputs)
    audio_mfccs = []
    len_audios = []
    title_ids = []
    title_masks  = []
    query_ids = []
    query_data_labels = []
    query_masks = []
    tag1s = []
    tag2s = []
    videos = []
    len_videos = []

    for input in inputs:
        ss = time.time()
        nid = input.get('DATA_NID')
        data_audio = input.get('DATA_AUDIO')
        #audio_mfcc, len_audio = get_mask_audio(input.get('DATA_AUDIO'))
        #title_id, title_mask = get_mask_title(input.get('DATA_TAG'))
        data_tag = input.get('DATA_TAG')
        ss2 = time.time()
        #query_id, query_data_label, query_mask = get_query_label_mask(nid)
        ss3 = time.time()
        tag1 = input.get('DATA_CATEGORY')
        tag2 = input.get('DATA_SUB_CATEGORY')
        #video, len_video = get_mask_video(input.get('DATA_VIDEO_FRAMES', [])[:max_num_image])
        data_video_frame = input.get('DATA_VIDEO_FRAMES', [])[:max_num_image]
        ee = time.time()
        #print("get_mask_video", ee - ss)
        data_nids.append(nid)
        data_audios.append(data_audio)
        data_video_frames.append(data_video_frame)
        #audio_mfccs.append(audio_mfcc)
        #len_audios.append(len_audio)
        data_tags.append(data_tag)
        #title_ids.append(title_id)
        #title_masks.append(title_mask)
        #query_ids.append(query_id)
        #query_data_labels.append(query_data_label)
        #query_masks.append(query_mask)
        #videos.append(video)
        #len_videos.append(len_video)
        ee = time.time()
        #print("for time" , ee - ss, ss2 - ss, ss3-ss2)

    s2 = time.time()
    s22 = time.time()
    results = executor.map(get_mask_audio, data_audios)
    results2 = executor.map(get_mask_video, data_video_frames)
    results3 = executor.map(get_mask_title, data_tags)
    results4 = executor.map(get_query_label_mask, data_nids)
    s3 = time.time()
    for result in results:
        audio_mfcc, len_audio = result
        audio_mfccs.append(audio_mfcc)
        len_audios.append(len_audio)
    s4 = time.time()
    for result in results2:
        video, len_video = result
        videos.append(video)
        len_videos.append(len_video)
    s5 = time.time()
    for result in results3:
        title_id, title_mask = result
        title_ids.append(title_id)
        title_masks.append(title_mask)
    s6 = time.time()
    for result in results4:
        query_id, query_data_label, query_mask = result
        query_ids.append(query_id)
        query_data_labels.append(query_data_label)
        query_masks.append(query_mask)
    del inputs
    gc.collect()
    outputs = {
        "audio_mfccs": audio_mfccs,
        "len_audios": len_audios,
        "videos": videos,
        "len_videos": len_videos,
        "title_ids": title_ids,
        "title_masks": title_masks,
        "query_ids": query_ids,
        "query_data_labels": query_data_labels,
        "query_masks": query_masks,

    }
    return outputs

def vqa_collate(feats):

    audio_mfccs = []
    len_audios = []
    videos = []
    len_videos = []
    title_ids = []
    title_masks = []
    query_ids = []
    query_data_labels = []
    query_masks = []
    for feat in feats:
        audio_mfccs.extend(feat.get("audio_mfccs"))
        len_audios.extend(feat.get("len_audios"))
        videos.extend(feat.get("videos"))
        len_videos.extend(feat.get("len_videos"))
        title_ids.extend(feat.get("title_ids"))
        title_masks.extend(feat.get("title_masks"))
        query_ids.extend(feat.get("query_ids"))
        query_data_labels.extend(feat.get("query_data_labels"))
        query_masks.extend(feat.get("query_masks"))
    s7 = time.time()
    input_len = len(audio_mfccs)
    #txt_lens = [i.size(0) for i in input_ids]
    #print(text)
    if len(title_ids[0]) != 0:
        title_ids = pad_sequence(title_ids, batch_first=True, padding_value=0)
        title_position_ids = torch.arange(0, title_ids.size(1), dtype=torch.long
                                    ).unsqueeze(0).repeat(input_len,1)
        title_attn_masks = pad_sequence(title_masks, batch_first=True, padding_value=0)
    else:
        title_ids = torch.zeros(0, dtype=torch.long)
        title_position_ids = torch.zeros(0, dtype=torch.long)
        title_attn_masks = torch.zeros(0, dtype=torch.long)

    if len(query_ids[0]) != 0:
        query_ids = pad_sequence(query_ids, batch_first=True, padding_value=0)
        query_position_ids = torch.arange(0, query_ids.size(1), dtype=torch.long
                                    ).unsqueeze(0).repeat(input_len,1)
        query_attn_masks = pad_sequence(query_masks, batch_first=True, padding_value=0)
    else:
        query_ids = torch.zeros(0, dtype=torch.long)
        query_position_ids = torch.zeros(0, dtype=torch.long)
        query_attn_masks = torch.zeros(0, dtype=torch.long)
    #print(input_ids, text_attn_masks)

    query_data_labels = torch.stack(query_data_labels, dim=0)
    #print(targets)
    img_feat = pad_tensors_img(videos, len_videos)
    #img_feat = img_feat.permute(0, 2, 1, 3, 4).contiguous()
    if len(img_feat) !=0:
        bs, tmp_image_mask_shape, c, h, w = img_feat.shape
        img_feat = img_feat.permute(0, 2, 1, 3, 4).contiguous()
        image_patch_number = (tmp_image_mask_shape // temporal_patch_size) * ((w // spatial_patch_size) ** 2)
        video_attn_masks = torch.zeros([bs, image_patch_number], dtype=torch.long)
        for i, t in enumerate(len_videos):
            tmp_patch_num = (t // temporal_patch_size) * ((w // spatial_patch_size) ** 2)
            video_attn_masks.data[i, :tmp_patch_num] = 1
        video_position_ids = _build_vid_pos_ids(tmp_image_mask_shape // temporal_patch_size, w // spatial_patch_size, w // spatial_patch_size).repeat(input_len, 1, 1)
    else:
        video_position_ids = torch.zeros(0, dtype=torch.long)
        video_attn_masks = torch.ones(0, dtype=torch.long)

    audio_feat = pad_tensors_audio(audio_mfccs, len_audios)
    if len(audio_feat) != 0:
        bs, _, tmp_audio_mask_shape = audio_feat.shape
        audio_patch_number = tmp_audio_mask_shape // audio_temporal_patch_size
        audio_attn_masks_ = torch.zeros([bs, audio_patch_number], dtype=torch.long)
        for i, t in enumerate(len_audios):
            tmp_patch_num = t//audio_temporal_patch_size
            audio_attn_masks_.data[i, :audio_temporal_patch_size] = 1
        audio_position_ids = torch.arange(0, audio_patch_number, dtype=torch.long).unsqueeze(0).repeat(input_len,1)
    else:
        audio_attn_masks_ = torch.zeros(0, dtype=torch.long)
        audio_position_ids = torch.ones(0, dtype=torch.long)

    #bs, max_tl = input_ids.size()
    #out_size = attn_masks.size(1)
    #gather_index = get_gather_index(txt_lens, num_bbs, bs, max_tl, out_size)
    e = time.time()
    #print("end vqa_collate")
    gc.collect()

    batch = {'title_id': title_ids,
             'title_position_id': title_position_ids,
             'title_attn_mask': title_attn_masks,
             'query_id':query_ids,
             'query_position_id':query_position_ids,
             'query_attn_mask':query_attn_masks,
             'img_feat': img_feat,
             'video_position_id': video_position_ids,
             'video_attn_mask': video_attn_masks,
             'audio_feat': audio_feat,
             'audio_position_id': audio_position_ids,
             'audio_attn_mask': audio_attn_masks_,
             'target': query_data_labels}
    #print("clo time", e - s, s2 - s, s22 - s2, s3 - s22, s4 - s3, s5 - s4, s6 - s5, s7 - s6, e - s7)
    #print(time.time())
    return batch
if __name__ == "__main__":
    train_dataset = WFlowDataset(name="duanxiao_video_train", version=1)
    train_dataloader = DataLoader(train_dataset, batch_size=5,collate_fn=vqa_collate, pin_memory=True)
    #print("nids | video_frames_num | video_frames[0].shape | label")
    for ret in train_dataloader:
        print(ret.keys())
        print("title_id:",ret['title_id'])
        print("title_position_id:",ret['title_position_id'])
        print("title_attn_mask:",ret['title_attn_mask'])
        print("query_id:",ret['query_id'])
        print("query_position_id:",ret['query_position_id'])
        print('query_attn_mask:',ret['query_attn_mask'])
        print("img_feat shape:",ret['img_feat'].shape)
        print("video_position_id shape:",ret['video_position_id'].shape)
        print("video_attn_mask shape:",ret['video_attn_mask'].shape)
        print("audio_feat shape:",ret['audio_feat'].shape)
        print("audio_position_id shape:",ret['audio_position_id'].shape)
        print("audio_attn_mask shape:",ret['audio_attn_mask'].shape)
        print("target:",ret['target'])
        break
    #    for nid in ret["DATA_NID"]:
    #        print(nid)
    #        if nid not in query_nid:
    #            print(nid)
    #        if num%10000==0:
    #            print(num)
        #video_frames_num = ret["DATA_VIDEO_FRAMES_NUM"]
        #video_frames = ret["DATA_VIDEO_FRAMES"]
        #label = ret["DATA_LABEL"]
        #print(nids, "|", video_frames_num, "|", video_frames[0].shape, "|", label)


#!/usr/bin/env python3
import numpy as np
import matplotlib.pyplot as plt
from .KITConfig import KITConfig
from .kitdata import KITData
from .kitlodger import KITLodger
from collections import OrderedDict
from .Utils import kitutils
import itertools
import logging

class KITMatplotlib(object):

    def __init__(self, cfg=None,is_cfg_new=None):

        self.__graphs = []
        self.__lodgers = []

        self.logger = logging.getLogger(__name__)
        self.logger.setLevel(logging.DEBUG)

        # load style parameters from cfg file
        self.__initStyle(cfg)
        self.__is_cfg_new = is_cfg_new

    def __initStyle(self, cfg):
        """ Loads and sets various parameters from cfg file which are then used
            to create the desired plot.

        """
        self.cfg = cfg
        # Canvas Options
        self.canvasSize = kitutils.extractList(cfg['Canvas','CanvasSize'], 'float')

        # Pad Options
        self.grid = True
        self.gridOptions = ('w', '-', '0.5')
        self.padSize = kitutils.extractList(cfg['Canvas','PadSize'], 'float')

        # Title options
        self.title = cfg['Title','Title']
        self.titleFont = cfg['Title','Font']
        self.titleFontSize = cfg['Title','FontSize']
        self.titleFontStyle = cfg['Title','FontStyle']
        self.titleOffset = 1 + cfg['Title','Offset']/100.

        # Axis Options
        self.labelX = cfg['XAxis','Title']
        self.labelY = cfg['YAxis','Title']
        self.rangeX = kitutils.extractList(cfg['XAxis','Range'], "float")
        self.rangeY = kitutils.extractList(cfg['YAxis','Range'], "float")
        self.fontX = cfg['XAxis','Font']
        self.fontY = cfg['YAxis','Font']
        self.fontSizeX = cfg['XAxis','FontSize']
        self.fontSizeY = cfg['YAxis','FontSize']
        self.fontStyleX = cfg['XAxis','FontStyle']
        self.fontStyleY = cfg['YAxis','FontStyle']
        self.absX = cfg['XAxis','Abs']
        self.absY = cfg['YAxis','Abs']
        self.logX = cfg['XAxis','Log']
        self.logY = cfg['YAxis','Log']
        self.tickX = cfg['XAxis','SciTick']
        self.tickY = cfg['YAxis','SciTick']

        # Marker Options
        self.markerSize = cfg['Marker','Size']
        self.markerSet = kitutils.extractList(cfg['Marker','Set'])
        self.hollowMarker = kitutils.extractList(cfg['Marker','HollowMarker'])

        #Line options
        self.colorPalette = cfg['Line','ColorPalette']
        self.colorSet = kitutils.extractList(cfg['Line','Color'])
        self.lineWidth = cfg['Line','Width']
        self.lineStyle = kitutils.extractList(cfg['Line','Style'])
        self.err = cfg['Line','ErrorBars']

        # KITPlot specific options
        self.norm = kitutils.extractList(cfg['Misc','Normalization'])
        self.splitGraph = cfg['Misc','SplitGraph']

        # legend options
        self.__entryDict = cfg['Legend','EntryList']
        self.legPosition = cfg['Legend','Position']

        # sets
        self.markers = {'s': 'square', 'v': 'triangle_down', '^': 'triangle_up',
                        '<': 'triangle_left', '>': 'triangle_right',
                        '8': 'octagon', 'p': 'pentagon', '*': 'star',
                        'h': 'hexagon1', 'H': 'hexagon2',
                        'D': 'diamond', 'd': 'thin_diamond', 'P': 'plus_filled',
                        'X': 'x_filled'}
        self.lines = ['None', '-', '--', '-.', ':']
        self.colors = self.__initColor()

        return True

    def __initColor(self):

        # standard mpl colorSet
        mpl_std = ['r', 'g', 'b', 'c', 'm', 'y', 'k']
        # KITcolor dictionary
        self.KITcolor = kitutils.get_KITcolor()

        if self.colorPalette == "std":
            mpl_std_sorted = [item for (i,item) in sorted(zip(self.colorSet, mpl_std))]
            return mpl_std_sorted
        elif self.colorPalette == "KIT":
            return list(self.KITcolor.keys())
        else:
            print("Warning:::Invalid 'ColorPalette' value. Using KITcolor as default")
            return list(self.KITcolor.keys())

    def addGraph(self, arg):
        """ Converts data of KITData objects or lists into a respective formate
        and writes them into .__graphs. Lodgers are seperated and written into
        .__lodgers.

        Args: x, y or KITData

        """

        x = []
        y = []
        dx = []
        dy = []
        if isinstance(arg, KITData):
            if KITData().getRPunchDict() == None:
                # self.__files.append(arg)
                # toggle absolute mode
                if self.absX:
                    x = list(np.absolute(arg.getX()))
                else:
                    x = arg.getX()
                if self.absY:
                    y = list(np.absolute(arg.getY()))
                else:
                    y = arg.getY()
                # get error bars if present
                if arg.getdX() != [] and arg.getdY() != []:
                    dx = arg.getdX()
                    dy = arg.getdY()
                elif arg.getdX() == [] and arg.getdY() == []:
                    pass
                else:
                    raise ValueError("Check data table. Only 2 (x,y) or "
                                     "4 (x,y,dx,dy) coordinates are allowed.")
                # create graph list
                if dx == [] and dy == []:
                    self.__graphs.append([x, y])
                elif dx != [] and dy != []:
                    self.__graphs.append([x, y, dx, dy])
                else:
                    raise ValueError("z-error not implemented yet")
            # Rpunch
            else:
                raise ValueError("Dictionary error")

        elif isinstance(arg, list) and len(arg) in [2,4]:

            if self.absX:
                x = list(np.absolute(arg[0]))
            else:
                x = arg
            if self.absY:
                y = list(np.absolute(arg[1]))
            else:
                y = arg[1]
            if len(args) == 4:
                dx = arg[2]
                dy = arg[3]

            # create graph list
            if dx == [] and dy == []:
                self.__graphs.append([x, y])
            elif dx != [] and dy != []:
                self.__graphs.append([x, y, dx, dy])
            else:
                raise ValueError("z-error not implemented yet")

        # add lodger
        elif isinstance(arg, KITLodger):
            self.__lodgers.append(arg)

        else:
            raise ValueError("Cant add following graph: " + str(arg))

        return True

    def draw(self, fileList):
        """
        doc

        """

        # create self.__graphs list
        for i, dset in enumerate(fileList):
            self.addGraph(dset)

        # read and adjsut .__entryDict before drawing
        self.readEntryDict(len(self.__graphs),self.getDefaultEntryDict(fileList))

        # interpret all entries in single file as graphs instead of a singel graph
        if self.splitGraph is True and len(self.__graphs) == 1:
            self.__graphs = [list(item) for item in zip(*self.__graphs[0])]

            # adjust entryDict
            newLength = len(self.__graphs)
            if len(self.__entryDict) != newLength:
                self.__entryDict = OrderedDict([])
                for i in range(0,newLength):
                    self.__entryDict.update({str(i) : "Data"+str(i)})
                self.cfg["Legend","EntryList"] = self.__entryDict

        elif self.splitGraph is True and len(self.__graphs) != 1:
            print("Warning::Can only split single graph. Request rejected")

        # apply user defined normalization or manipulation of y values of each graph
        kitutils.manipulate(self.__graphs, self.norm)

        # create an empty canvas with canvas size in [inch]: 1 inch = 2.54 cm
        fig = plt.figure(figsize=list(map(lambda x: x/2.54, self.canvasSize)))

        # specify (nrows, ncols, axnum)
        ax = fig.add_subplot(1, 1, 1)

        # adjust pad size: [left, bottom, width, height]
        ax.set_position(self.padSize)

        # adjust axis tick
        if self.tickX:
            plt.ticklabel_format(style='sci', axis='x', scilimits=(0,0))
        if self.tickY:
            plt.ticklabel_format(style='sci', axis='y', scilimits=(0,0))

        for i, table in enumerate(self.__graphs):
            if isinstance(self.hollowMarker, list) and i in self.hollowMarker:
                markerface = 'white'
            else:
                markerface = self.getColor(i)
            ax.plot(table[0],                           # x-axis
                    table[1],                           # y-axis
                    color=self.getColor(i),             # line color
                    marker=self.getMarker(i),           # marker style
                    markersize=self.markerSize,
                    markerfacecolor=markerface,
                    linewidth=self.lineWidth,
                    linestyle=self.getLineStyle(i),
                    label=self.getLabel(i))
        # set error bars
        for i, table in enumerate(self.__graphs):
            if len(table) == 4 and self.err == True:
                ax.errorbar(table[0],table[1],xerr=table[2],yerr=table[3],
                            color=self.getColor(i),
                            elinewidth=1)
            elif len(table) != 4 and self.err == True:
                print("Warning::Can't find x- and y-errors in file. Request "
                      "rejected.")

        # set titles
        # weights = ['light', 'normal', 'medium', 'semibold', 'bold', 'heavy', 'black']
        ax.set_title(self.title,
                     fontsize=self.titleFontSize,
                     y=self.titleOffset,
                     fontweight=self.titleFontStyle)
        ax.set_xlabel(self.labelX,
                      fontsize=self.fontSizeX,
                      fontweight=self.fontStyleX)
        ax.set_ylabel(self.labelY,
                      fontsize=self.fontSizeY,
                      fontweight=self.fontStyleY)

        # set log styles
        if self.logX:
            ax.semilogx()
        if self.logY:
            ax.semilogy()

        # set grid
        if self.grid == True:
            # *args = [color,linstyle,linewidth]
            ax.grid()

        # set axis range manually
        if self.rangeX != 'auto':
            ax.set_xlim(self.rangeX)
        if self.rangeY != 'auto':
            ax.set_ylim(self.rangeY)

        self.setLegend(ax)

        # x = [graph[0][0] for graph in self.__graphs]
        # y = [graph[1][0] for graph in self.__graphs]
        # x = self.__graphs[0][0]
        # y = self.__graphs[0][1]
        # y = [y for y in self.__graphs[0][1]]
        # y2 = [y for y in self.__graphs[1][1] if y > 0.3e-12]
        # y3 = [y for y in self.__graphs[1][1] if y > 0.3e-12]
        # y = y1+y2+y3
        # print(y)
        # print(np.mean(y), np.std(y))
        # m,b = np.polyfit(x,y,1)
        # print(1/m,b)
        # t = np.arange(0,0.018,0.001)
        # f = m*t+b
        # ax.plot(t, f, color='black')
        # # ax.xaxis.get_children()[1].set_size(14)
        # ax.xaxis.get_children()[1].set_weight("bold")
        # ax.set_xticklabels
        # ax.axhline(y=12000,color=self.KITcolor['KITred'][3][1],linewidth=10,linestyle='-',zorder=0)
        # ax.axhline(y=8400,color=self.KITcolor['KITred'][3][1],linewidth=10,linestyle='-',zorder=0)
        # ax.axhline(y=2100,color=self.KITcolor['KITred'][3][1],linewidth=10,linestyle='-',zorder=0)
        return fig

    def setLegend(self, obj):

        # get names from cfg and lodger labels
        graphEntries = [items[1] for items in list(self.__entryDict.items())]

        total_len = len(self.__graphs)

        # reorder legend items according to 'EntryList'
        handles,labels = obj.get_legend_handles_labels()
        # handles = self.adjustOrder(handles)
        # labels = self.adjustOrder(labels)
        handles = kitutils.adjustOrder(handles, self.__entryDict, total_len)
        labels = kitutils.adjustOrder(labels, self.__entryDict, total_len)

        if self.legPosition == "auto":
            obj.legend(handles,labels)
        elif self.legPosition == "TL":
            obj.legend(handles,labels,loc='upper left')
        elif self.legPosition == "BL":
            obj.legend(handles,labels,loc='lower left')
        elif self.legPosition == "TR":
            obj.legend(handles,labels,loc='upper right')
        elif self.legPosition == "BR":
            obj.legend(handles,labels,loc='lower right')
        elif self.legPosition == "test2":
            obj.legend(handles,labels,bbox_to_anchor=(0., 1.17, 1., .102),
                       loc='upper right',ncol=3, mode="expand", borderaxespad=0.)
        elif self.legPosition == "test":
            obj.legend(handles,labels,bbox_to_anchor=(0., 0.,1.,1.),
                       loc='lower left',ncol=3, mode="expand", borderaxespad=0.)
        elif self.legPosition == "below":
            obj.legend(handles,labels,bbox_to_anchor=(0., -0.32, 1., .102),
                       loc='lower center',ncol=3, mode="expand", borderaxespad=0.)
        return True

    def getLabel(self, index):

        label = [items[1] for items in list(self.__entryDict.items())]
        return label[index]

    def getMarker(self, index):
        """ Returns a valid marker value for matplotlib's plot() function. If
        'MarkerSet' is a list, the method will cycle the list's items until all
        graphs are taken care of.

            Args:
                index (int): represents an iterator marking a certain graph in
                             .__graphs
        """

        try:
            # assign same marker to all graphs
            if isinstance(self.markerSet, int):
                return list(self.markers.keys())[self.markerSet]
            # cycle list of strings
            elif all(isinstance(item, str) for item in self.markerSet):
                for i, item in enumerate(itertools.cycle(self.markerSet)):
                    if index == i:
                        return item
            # cycle list of integers
            elif all(isinstance(item, int) for item in self.markerSet):
                for i, item in enumerate(itertools.cycle(self.markerSet)):
                    if index == i:
                        return list(self.markers.keys())[item]
        except:
            print("Warning:::Invalid value in 'MarkerSet'. Using default instead.")
            return list(self.markers.keys())[index]

    def getColor(self, index):

        try:
            # self.colors represents color_keys in KITcolor
            if all(isinstance(item, int) for item in self.colorSet) \
                        and isinstance(self.colorSet, list):
                for i, item in enumerate(itertools.cycle(self.colorSet)):
                    if i == index:
                        color = self.KITcolor[self.colors[item]][0][1]
                        return color

            # if colors in 'ColorSet' are strings and correspond to entries
            # in KITcolor dict
            elif all(isinstance(item, str) for item in self.colorSet) \
                        and isinstance(self.colorSet, list):
                # in case there are less entries in colorSet than needed we n
                # eed to cycle that list
                for i, cycled in enumerate(itertools.cycle(self.colorSet)):
                    if i == index:
                        color = cycled
                        break
                # search for RGB values in KITcolor dict for given color key
                for colorDict in list(self.KITcolor.values()):
                    try:
                        return colorDict[color]
                    except:
                        pass
                raise Exception
                color
        except:
            print("Warning:::Invalid input in 'ColorSet'. Using default instead.")
            for i, color in enumerate(itertools.cycle(self.colors)):
                if i == index:
                    return list(self.KITcolor[color].values())[0]

    def getLineStyle(self, index):

        try:
            if isinstance(self.lineStyle, int):
                return self.lines[self.lineStyle]
            elif all(isinstance(item, str) for item in self.lineStyle) \
                    and isinstance(self.lineStyle, list):
                for i, item in enumerate(itertools.cycle(self.lineStyle)):
                    if item not in self.lines:
                        raise ValueError
                    if index == i:
                        return item
            elif all(isinstance(item, int) for item in self.lineStyle) \
                    and isinstance(self.lineStyle, list):
                for i, item in enumerate(itertools.cycle(self.lineStyle)):
                    if index == i:
                        return self.lines[item]
        except:
            print("Warning:::Invalid value in 'LineStyle'. Using default instead.")
            return self.lines[1]

    def getGraphList(self):
        return self.__graphs

    def readEntryDict(self, exp_len, def_list):
        """'EntryList' makes the names and order of all graphs accessible. This
        subsection is read every time KITPlot is executed. An empty value ("")
        can be used to reset the entry to its default value (the original order
        and names given by .__files).
        """
        # writes entry dict to cfg and sets it back to default if value is ""
        if self.cfg['Legend','EntryList'] == "":
            self.cfg['Legend','EntryList'] = def_list
            self.__entryDict = def_list
            if self.__is_cfg_new == False:
                print("EntryDict was set back to default!")

        # calculate expected number of entries in 'EntryList'
        if len(self.__entryDict) != exp_len and self.splitGraph == False:
            raise KeyError("Unexpected 'EntryList' value! Number of graphs and "
                           "entries does not match or a key is used more than"
                           "once. Adjust or reset 'EntryList'.")
        return True

    def fixEntryDict(self):

        # get key list from 'EntryList'
        keys = [int(key) for key in self.__entryDict.keys()]

        # key list should start at 0 and should have a length of len(keys)
        straight_list = list(range(len(keys)))
        # print("fix", straight_list)

        # get reference list in respect to the original order of key list
        ref_list = [y for (x,y) in sorted(zip(keys, straight_list))]

        # reorder reference list so that values stay in the same order as before
        fixed_order = [y for (x,y) in sorted(zip(ref_list, straight_list))]

        values = list(self.__entryDict.values())
        new = OrderedDict(zip(fixed_order, values))
        self.__cfg['Legend','EntryList'] = new

    def getDefaultEntryDict(self,List):
        """ Loads default names and order in respect to the KITData objects
        in 'self.__files' list. Both keys and values of the dictionary must be
        strings.

        """
        entryDict = OrderedDict()
        # write legend entries in a dict
        for i, graph in enumerate(List):
            entryDict[i] = str(graph.getName())

        return entryDict


import os
import io
import imageio
import logging

import cv2
import numpy as np
from PIL import Image
import torch
from ts.torch_handler.base_handler import BaseHandler

logger = logging.getLogger(__name__)

def test_resize(img, size=640, pad=False):
    h, w, c = img.shape
    scale_w = size / w
    scale_h = size / h
    scale = min(scale_w, scale_h)
    h = int(h * scale)
    w = int(w * scale)

    new_img = None
    if pad:
        new_img = np.zeros((size, size, c), img.dtype)
        new_img[:h, :w] = cv2.resize(img, (w, h))
    else:
        new_img = cv2.resize(img, (w, h))

    return new_img

def test_preprocess(img,
                    mean=[103.939, 116.779, 123.68],
                    to_tensor=True,
                    pad=False):
    img = test_resize(img, size=640, pad=pad)

    img = img.astype(np.float32)
    img[..., 0] -= mean[0]
    img[..., 1] -= mean[1]
    img[..., 2] -= mean[2]
    img = np.expand_dims(img, axis=0)

    if to_tensor:
        img = torch.Tensor(img.transpose(0, 3, 1, 2))

    return img

class DBTextDetectionHandler(BaseHandler):
    def __init__(self):
        self.model = None
        self.device = None
        self.initialized = False

    def initialize(self, ctx):
        self.manifest = ctx.manifest
        properties = ctx.system_properties
        self.device = torch.device('cpu')
        model_dir = properties.get("model_dir")
        serialized_file = self.manifest['model']['serializedFile']
        model_pt_path = os.path.join(model_dir, serialized_file)
        assert os.path.exists(model_pt_path)
        self.model = torch.jit.load(model_pt_path)
        self.model.to(self.device)
        self.model.eval()

        logger.debug(
            'Model file {0} loaded successfully'.format(model_pt_path))
        self.initialized = True

    def preprocess(self, request):

        tensor_imgs = []
        for _, data in enumerate(request):
            image = data.get("data")
            if image is None:
                image = data.get("body")

            input_image = Image.open(io.BytesIO(image))
            input_image = np.array(input_image)
            tensor_img = test_preprocess(input_image, pad=False)
            tensor_imgs.append(tensor_img)

        tensor_imgs = torch.cat(tensor_imgs)
        return tensor_imgs

    def inference(self, img):
        return self.model(img)

    def postprocess(self, data):

        res = []
        data = data.detach().cpu().numpy()
        for pred in data:
            prob_mask = (pred[0] * 255).astype(np.uint8)
            thresh_mask = (pred[1] * 255).astype(np.uint8)
            prob_mask = prob_mask.tolist()
            thresh_mask = thresh_mask.tolist()
            res.append({"prob_mask": prob_mask, "thresh_mask": thresh_mask})

        return res

_service = DBTextDetectionHandler()

def handle(data, context):
    if not _service.initialized:
        _service.initialize(context)

    if data is None:
        return None

    data = _service.preprocess(data)
    data = _service.inference(data)
    data = _service.postprocess(data)

    return data


# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

bl_info = {
    "name" : "SurfaceAdaptiveRTI",
    "author" : "Ramamoorthy Luxman",
    "description" : "Virtual surface adaptive RTI (NBLP)",
    "blender" : (3, 1, 2),
    "version" : (0, 0, 1),
    "location" : "3D View > Tools > SurfaceAdaptiveRTI",
    "warning" : "",
    "category" : "3D View"
}

import os
from random import sample
import bpy
import numpy as np
import math
import time
import cv2
import numpy as np
from mathutils import Vector
import matplotlib.pyplot as plot
import yaml
from itertools import islice
from bpy.props import (StringProperty,
                       BoolProperty,
                       IntProperty,
                       FloatProperty,
                       FloatVectorProperty,
                       EnumProperty,
                       PointerProperty,
                       )
from bpy.types import (Panel,
                       Operator,
                       PropertyGroup,
                       )

#################################### Global vars { ################################
updated_lps = []
#################################### Global vars } ################################

#################################### Helpers { ####################################

# def cart2sph(x, y, z):
def cart2sph(*args):
    x = args[0]
    y = args[1]
    z = 1.0
    if len(args)>2:
        z = args[2]
    print(x,",",y,",",z)
    hxy = np.hypot(x, y)
    r = np.hypot(hxy, z)
    el = np.arctan2(z, hxy)
    az = np.arctan2(y, x)
    return r, az, el

def sph2cart(az, el, r):
    rcos_theta = r * np.cos(el)
    x = rcos_theta * np.cos(az)
    y = rcos_theta * np.sin(az)
    z = r * np.sin(el)
    return x, y, z

# Reads the standard .lp file to extract the light positions.
# This function calculates just the directions - thetas and phis. The radius of the dome in the actual lp file is ignored and considers the radius passed to this function only to recreate new light positions.
def read_lp_file(file_path, dome_radius):
    light_positions = []
    # Read in .lp data

    if not os.path.exists(file_path):
        return light_positions

    try:
        file = open(file_path)
    except RuntimeError as ex:
        error_report = "\n".join(ex.args)
        print("Caught error:", error_report)
        return {'ERROR'}

    rows = file.readlines()
    file.close()

    numLights = int(rows[0].split()[0])

    for idx in range(1, numLights + 1):
        cols = rows[idx].split()
        x = float(cols[1])
        y = float(cols[2])
        z = float(cols[3])
        r, long, lat = cart2sph(x,y,z)
        r = dome_radius
        x, y, z = sph2cart(long,lat,r)
        light_positions.append((float(x),float(y),float(z)))

    return light_positions

############################ Generate n evenly spaced hemispherical points { ##################################

def generate_n_evenly_spaced_hemispherical_points(samples = 45):
    samples = 2*samples
    phi = math.pi * (3. - math.sqrt(5.))  # golden angle in radians
    cartesian_points = []
    polar_points = []
    for i in range(int(samples/2), samples):
        z = -1 + (i / float(samples - 1)) * 2  # z goes from 0 to 1
        radius = math.sqrt(1 - z * z)  # radius at z
        theta = phi * i  # golden angle increment
        y = math.cos(theta) * radius
        x = math.sin(theta) * radius
        r, az, el = cart2sph(x,y,z)
        # convert the angles to be in the range 0 to 2 pi
        az = (az + np.pi) % (2 * np.pi) - np.pi
        el = (el + np.pi) % (2 * np.pi) - np.pi
        polar_points.append((float(az),float(el)))
        cartesian_points.append((x, y, z))

    # plot.figure().add_subplot(111, projection='3d').scatter([p[0] for p in cartesian_points], [p[1] for p in cartesian_points], [p[2] for p in cartesian_points]);
    # plot.show()

    return polar_points, cartesian_points

############################ Generate n evenly spaced hemispherical points } ##################################

def generate_homogenous_points_along_theta(samples = 10, dome_radius=1.0, phi=45.0):
        light_positions_cartesian = []
        light_positions_polar = []
        dome_radius = dome_radius
        phi = math.radians(phi)
        if samples%2 != 0:
            samples = samples+1
        for i in range(0,samples):
            theta = i*(math.radians(360.0)/samples)
            x, y, z = sph2cart(theta, phi, dome_radius)
            light_positions_cartesian.append((x,y,z))
            light_positions_polar.append((theta, phi))

        return  light_positions_polar, light_positions_cartesian

#################################### Helpers } ####################################

#################################### NBLP algorithms { ####################################

#################################### NBLP Basic { ####################################

class Nblp:
    iterations = []
    class iteration:
        def __init__(self, lps_polar, lps_cartesian, iteration_nb):
            self.lps_polar = lps_polar
            self.nb_images = len(lps_polar)
            self.lps_cartesian = lps_cartesian
            self.filenames_subtext = "nblp_iteration_"+str(iteration_nb)+"_"
            self.iteration_nb = iteration_nb

        def plot_lps(self):
            fig, ax = plot.subplots(subplot_kw={'projection': 'polar'})
            for i in range(0,self.nb_images):
                theta = self.lps_polar[i][0]
                radius = math.degrees(self.lps_polar[i][1])
                ax.plot(theta, radius,"o")
                ax.set_rmax(2)
                ax.set_rticks([90, 60, 30, 0])
                ax.set_rlabel_position(-22.5)
                ax.grid(True)
                ax.set_title("Light positions projected to 2D plane")
            return plot

        def rename_files(self, path):
            for i in range(0, self.nb_images):
                theta=str(math.floor(100*math.degrees(self.lps_polar[i][0]))/100)
                phi=str(math.floor(100*math.degrees(self.lps_polar[i][1]))/100)
                old_name = path+"nblp_iteration_"+str(self.iteration_nb)+"_"+str(i+1)+".png"
                new_name = path+"nblp_iteration_"+str(self.iteration_nb)+"_"+str(i+1)+"_theta_" + theta + "_phi_" + phi + ".png"
                os.rename(old_name, new_name)

    def __init__(self):
        iteration_nb = len(self.iterations)
        lps_polar, lps_cartesian = self.generate_homogenous_points_along_theta(n=45, dome_radius=1, phi=math.radians(45.0), iteration_nb=iteration_nb)
        step = self.iteration(lps_polar, lps_cartesian, iteration_nb)
        self.iterations.append(step)

    def dense_acquisition(self):
        iteration_nb = len(self.iterations)
        lps_polar, lps_cartesian = self.generate_homogenous_points_along_theta(n=45, dome_radius=1, phi=math.radians(45.0), iteration_nb=iteration_nb)
        step = self.iteration(lps_polar, lps_cartesian, iteration_nb)
        self.iterations.append(step)

    def generate_homogenous_points_along_theta(self, n, dome_radius, phi, iteration_nb):
        light_positions_cartesian = []
        light_positions_polar = []
        self.dome_radius = dome_radius
        if n%2 != 0:
            n = n+1
        for i in range(0,n):
            theta = i*(math.radians(360.0)/n)
            x, y, z = sph2cart(theta, phi, dome_radius)
            light_positions_cartesian.append((x,y,z))
            light_positions_polar.append((theta, phi))

        return  light_positions_polar, light_positions_cartesian

    def calculate_entropies(self,iteration_nb, file_path):
        print("Calculating entropies")
        img_path = file_path+"\\..\\"+self.iterations[iteration_nb].filenames_subtext+str(1)+".png"
        img_sum = cv2.imread(img_path)
        for i in range(0, self.iterations[iteration_nb].nb_images):
            img_path = file_path+"\\..\\"+self.iterations[iteration_nb].filenames_subtext+str(i+1)+".png"
            print(img_path)
            img = cv2.imread(img_path)
            img_sum = cv2.addWeighted(img_sum,0.5,img,0.5,0)

        for i in range(0, self.iterations[iteration_nb].nb_images):
            img_path = file_path+"\\..\\"+self.iterations[iteration_nb].filenames_subtext+str(i+1)+".png"
            img_diff = cv2.absdiff(img_sum,cv2.imread(img_path))
            normalized_img_diff = np.zeros(img_diff.shape)
            # min_val = img_diff[..., 0].min()
            # max_val = img_diff[..., 0].max()
            min_val = img_diff.min()
            max_val = img_diff.max()
            normalized_img_diff = img_diff * (255/(max_val-min_val))
            # cv2.normalize(img_diff, normalized_img_diff, min_val, max_val, cv2.NORM_MINMAX)
            if not os.path.exists(file_path):
                os.makedirs(file_path)
                cv2.imwrite(file_path+self.iterations[iteration_nb].filenames_subtext+str(i)+".png", img_diff)
                cv2.imwrite(file_path+self.iterations[iteration_nb].filenames_subtext+str(i)+"_normalized.png", normalized_img_diff)

    def generate_lp_file(self, iteration_nb, file_path):
        data = str(self.iterations[iteration_nb].nb_images)
        for i in range(0, self.iterations[iteration_nb].nb_images):
            step =self.iterations[iteration_nb]
            data = data+"\n"+step.filenames_subtext+str(i+1)+".png\t"+str(step.lps_cartesian[i][0])+"\t"+str(step.lps_cartesian[i][1])+"\t"+str(step.lps_cartesian[i][2])
        with open(file_path, 'w') as f:
            f.write(data)

    def write_log(self, path):
        with open(path, 'w') as file:
            iterations = []
            for i in range(0, len(self.iterations)):
                iteration = {{'iteration nb': i},
                             {'lps_polar': self.iterations[i].lps_polar},
                             {'nb_images': self.iterations[i].nb_images},
                             {'lps_cartesian':self.iterations[i].lps_cartesian},
                             {'filenames_subtext': self.iterations[i].filenames_subtext},
                             {'iteration_nb':self.iterations[i].iteration_nb}}

                iterations.append(iteration)
            yaml.dump(iterations, file)

#################################### NBLP Basic } ####################################

#################################### NBLP Basic approach 2 = Relighting loss gradient descent approach { ####################################

class Nblp_2:
    iterations = []
    class iteration:
        def __init__(self, lps_polar, lps_cartesian, iteration_nb, context):
            self.lps_polar = lps_polar
            self.nb_images = len(lps_polar)
            self.lps_cartesian = lps_cartesian
            self.filenames_subtext = "nblp_iteration_"+str(iteration_nb)+"_"
            self.iteration_nb = iteration_nb
            self.context = context
            self.generate_file_names()
            # self.loss = 1.0

        def plot_lps(self):
            fig, ax = plot.subplots(subplot_kw={'projection': 'polar'})
            for i in range(0,self.nb_images):
                theta = self.lps_polar[i][0]
                radius = math.degrees(self.lps_polar[i][1])
                ax.plot(theta, radius,"o")
                ax.set_rmax(2)
                ax.set_rticks([90, 60, 30, 0])
                ax.set_rlabel_position(-22.5)
                ax.grid(True)
                ax.set_title("Light positions projected to 2D plane")
            return plot

        def rename_files(self, path):
            for i in range(0, self.nb_images):
                theta=str(math.floor(100*math.degrees(self.lps_polar[i][0]))/100)
                phi=str(math.floor(100*math.degrees(self.lps_polar[i][1]))/100)
                old_name = path+"nblp_iteration_"+str(self.iteration_nb)+"_"+str(i+1)+".png"
                new_name = path+self.file_names[i]
                os.rename(old_name, new_name)

        def generate_lp_file(self, file_path):
            data = str(self.nb_images)
            for i in range(0, self.nb_images):
                data = data+"\n"+self.file_names[i]+"\t"+str(self.lps_cartesian[i][0])+"\t"+str(self.lps_cartesian[i][1])+"\t"+str(self.lps_cartesian[i][2])
            with open(file_path, 'w') as f:
                f.write(data)

        def calculate_entropies(self,iteration_nb, file_path):
            print("Calculating entropies")
            img_path = file_path+"\\..\\"+self.file_names[0]
            img_sum = cv2.imread(img_path)
            for i in range(0, self.nb_images):
                img_path = file_path+"\\..\\"+self.file_names[i]
                print(img_path)
                img = cv2.imread(img_path)
                img_sum = cv2.addWeighted(img_sum,0.5,img,0.5,0)

            for i in range(0, self.nb_images):
                img_path = file_path+"\\..\\"+self.file_names[i]
                img_diff = cv2.absdiff(img_sum,cv2.imread(img_path))
                normalized_img_diff = np.zeros(img_diff.shape)
                # min_val = img_diff[..., 0].min()
                # max_val = img_diff[..., 0].max()
                min_val = img_diff.min()
                max_val = img_diff.max()
                normalized_img_diff = img_diff * (255/(max_val-min_val))
                # cv2.normalize(img_diff, normalized_img_diff, min_val, max_val, cv2.NORM_MINMAX)
                if not os.path.exists(file_path):
                    os.makedirs(file_path)
                    cv2.imwrite(file_path+self.file_names[i], img_diff)
                    cv2.imwrite(file_path+self.file_names[i].split(".")[0]+"_normalized.png", normalized_img_diff)

        def generate_file_names(self):
            self.file_names = []
            for i in range(0, self.nb_images):
                theta=str(math.floor(100*math.degrees(self.lps_polar[i][0]))/100)
                phi=str(math.floor(100*math.degrees(self.lps_polar[i][1]))/100)
                self.file_names.append("nblp_iteration_"+str(self.iteration_nb)+"_"+str(i+1)+"_theta_" + theta + "_phi_" + phi + ".png")

        def execute_acq(self):
            global updated_lps
            updated_lps = self.lps_cartesian
            bpy.context.scene.render.filepath = self.context.scene.acquisition_panel.output_path+self.filenames_subtext+"#.png"
            bpy.ops.rti.create_lights()
            bpy.context.scene.frame_end = len(self.context.scene.light_panel.light_list)
            bpy.ops.rti.set_animation()
            bpy.ops.render.render(animation=True, use_viewport = True, write_still=True)
            bpy.ops.render.play_rendered_anim()
            self.plot_lps().savefig(self.context.scene.acquisition_panel.output_path+"iteration_"+str(self.iteration_nb)+"_"+str(self.nb_images)+".png")
            self.rename_files(self.context.scene.acquisition_panel.output_path)
            self.generate_lp_file(file_path=self.context.scene.acquisition_panel.output_path+"iteration_"+str(self.iteration_nb)+".lp")
            self.calculate_entropies(self.iteration_nb,self.context.scene.acquisition_panel.output_path+"\\entropies\\")

    def __init__(self, context):
        path = context.scene.acquisition_panel.output_path
        self.context = context
        if not os.path.exists(path +"log.yaml"):
            # lps_polar, lps_cartesian = generate_n_evenly_spaced_hemispherical_points(samples=25)
            lps_polar, lps_cartesian = generate_homogenous_points_along_theta(samples=10)
            step = self.iteration(lps_polar=lps_polar, lps_cartesian=lps_cartesian, iteration_nb=0, context=context)
            step.execute_acq()
            self.iterations.append(step)
        else:
            print(path + "log.yaml")
            data = None
            with open(path + "log.yaml") as f:
                data = yaml.load(f.read(), yaml.loader.Loader)

            for i in range(0, len(data)):
                step = self.iteration(data[i]['lps_polar'], data[i]['lps_cartesian'], len(data)-1, context=context)
                self.iterations.append(step)

            if os.path.exists(path + "next_iteration.lp"):
                lps_cartesian = read_lp_file(path + "next_iteration.lp", 1.0)
                print(len(lps_cartesian))
                lps_polar = []
                for i in range(0, len(lps_cartesian)):
                    r, az, el = cart2sph(lps_cartesian[i][0], lps_cartesian[i][1], lps_cartesian[i][2])
                    lps_polar.append((float(az),float(el)))
                step = self.iteration(lps_polar=lps_polar, lps_cartesian=lps_cartesian, iteration_nb=len(data), context=context)
                step.execute_acq()
                self.iterations.append(step)

        # for i in range(0,len(self.iterations)):

        #     data = str(self.nb_images)
        #     for i in range(0, self.nb_images):
        #         data = data+"\n"+self.file_names[i]+"\t"+str(self.lps_cartesian[i][0])+"\t"+str(self.lps_cartesian[i][1])+"\t"+str(self.lps_cartesian[i][2])
        #     with open(file_path, 'w') as f:
        #         f.write(data)

    def dense_acquisition(self):
        iteration_nb = len(self.iterations)
        lps_polar, lps_cartesian = self.generate_homogenous_points_along_theta(n=100, dome_radius=1, phi=math.radians(50.0), iteration_nb=iteration_nb)
        step = self.iteration(lps_polar, lps_cartesian, iteration_nb)
        self.iterations.append(step)

    def write_log(self, path):
        open(path, "w").close()
        with open(path, 'w') as file:
            iterations = []
            for i in range(0, len(self.iterations)):
                iteration = {'lps_polar': list(self.iterations[i].lps_polar),
                             'nb_images': self.iterations[i].nb_images,
                             'lps_cartesian': list(self.iterations[i].lps_cartesian),
                             'filenames_subtext': self.iterations[i].filenames_subtext,
                             'iteration_nb':self.iterations[i].iteration_nb,
                             'file_names': self.iterations[i].file_names}

                iterations.append(iteration)
            yaml.dump(iterations, file)

#################################### NBLP Basic approach 2 = Relighting loss } ####################################

#################################### NBLP algorithms } ####################################

#################################### PropertyGroups { ####################################
class light(bpy.types.PropertyGroup):
    light = bpy.props.PointerProperty(name="Light object",
                                      type = bpy.types.Object,
                                      description = "A light source")

class camera(bpy.types.PropertyGroup):
    camera = bpy.props.PointerProperty(name="Camera object",
                                       type = bpy.types.Object,
                                       description = "Camera")

class surface(bpy.types.PropertyGroup):
    surface = bpy.props.PointerProperty(name="Surface object",
                                       type = bpy.types.Object,
                                       description = "Surface")

#################################### PropertyGroups } ####################################

#################################### Menu { ####################################
class lightSettings(PropertyGroup):

    nblp: BoolProperty(
        name="NBLP",
        description="NBLP algorithm generated Light Positions",
        default=True,
    )

    lp_file_path : StringProperty(
        name="LP file",
        subtype="FILE_PATH",
        description="File path for light positions file (.lp)",
        default="",
        maxlen=1024,
    )

    dome_radius : FloatProperty(
        name="RTI dome radius",
        description="Radius of RTI dome [m]",
        default=1.0
    )

    light_strength : FloatProperty(
        name="Light strength",
        description="Strength of the light source",
        default=0.5
    )

    light_positions = []
    light_list = []

class surfaceSettings(PropertyGroup):

    def update(self, context):
        mat = context.scene.objects[context.scene.surface_panel.surface[0]].active_material
        node_tree = mat.node_tree
        nodes = node_tree.nodes
        bsdf = nodes.get("Principled BSDF")
        bsdf.inputs[6].default_value=self.metallic
        bsdf.inputs[7].default_value=self.specularity
        bsdf.inputs[9].default_value=self.roughness

    mesh_file_path : StringProperty(
        name="Surface mesh file",
        subtype="FILE_PATH",
        description="File path for the surface mesh file",
        default="",
        maxlen=1024
    )

    texture_image_path : StringProperty(
        name="Surface texture image file",
        subtype="FILE_PATH",
        description="File path for the surface texture image file",
        default="",
        maxlen=1024

    )

    metallic : FloatProperty(
        name="Metallic",
        description="Surface metallic",
        default=0.0,
        min=0.0,
        max=1.0,
        update=update
    )

    roughness : FloatProperty(
        name="Roughness",
        description="Surface roughness",
        default=0.0,
        update=update,
        min=0.0,
        max=1.0,
    )

    specularity : FloatProperty(
        name="Specularity",
        description="Specularity",
        default=0.5,
        update=update,
        min=0.0,
        max=1.0,
    )

    surface = []
    surface_bbox = []

class cameraSettings(PropertyGroup):

    def update(self, context):
        cameras_obj= [cam for cam in bpy.data.objects if cam.type == 'CAMERA']
        # data = cameras_obj[0].data
        data = bpy.data.objects[context.scene.camera_panel.camera[0]].data
        data.lens = self.focal_length
        bpy.context.scene.render.resolution_y = int(self.aspect_ratio*bpy.context.scene.render.resolution_x)
        data.display_size = self.view_port_size
        data.sensor_width = self.sensor_size

    def update_camera_height(self,context):
        cameras_obj = [cam for cam in bpy.data.objects if cam.type == 'CAMERA']
        if len(cameras_obj) != 1:
            self.report({'ERROR'}, "Camera doesn't exist in scene or there is more than 1 camera.")
            return {'FINISHED'}

        cameras_obj[0].location[2] = self.camera_height

    camera_height : FloatProperty(
        name="Camera height",
        description="Camera position height",
        default=1,
        min=0,
        max=4.0,
        update=update_camera_height
    )

    aspect_ratio : FloatProperty(
        name="Aspect ratio",
        description="Aspect ratio of the sensor",
        default=1,
        min=0.000,
        max=2,
        update=update
    )

    focal_length : FloatProperty(
        name="Focal length",
        description="Focal length",
        default=199.6,
        min=0.000,
        max=1000,
        step=1,
        update=update
    )

    subject : PointerProperty(
        name="Focus on",
        description="Subject to focus",
        type=bpy.types.Object,

    )

    resolution_factor : FloatProperty(
        name="Resolution",
        description="Resolution scale factor",
        min=0,
        max=5,
        default=1.0,
        update=update
    )

    view_port_size : FloatProperty(
        name="View port size",
        description="View port size",
        min=0,
        max=100,
        default=0.04,
        update=update
    )

    sensor_size : FloatProperty(
        name="Sensor size",
        description="Sensor size",
        min=0,
        max=1000,
        default=8,
        update=update
    )

    camera = []

class acquisitionSettings(PropertyGroup):
    output_path : StringProperty(
        name="Output path",
        subtype="FILE_PATH",
        description="File path for saving the rti acquisition",
        default="",
        maxlen=1024
    )

    csvOutputLines = []
#################################### Menu } ####################################

#################################### Operators { ####################################

class reset_scene(Operator):
    bl_label = "Reset all"
    bl_idname = "rti.reset_scene"

    def execute(self, context):
        scene = context.scene
        for current_light in bpy.data.lights:
            bpy.data.lights.remove(current_light)
        for current_object in bpy.data.objects:
            bpy.data.objects.remove(current_object)

        return {"FINISHED"}

class createLights(Operator):
    bl_label = "Create lights"
    bl_idname = "rti.create_lights"

    def execute(self, context):
        global updated_lps
        scene = context.scene
        mytool = scene.light_panel
        mytool.light_list.clear()

        for current_light in bpy.data.lights:
            current_light.animation_data_clear()
            bpy.data.lights.remove(current_light)

        mytool.light_positions = updated_lps

        if not os.path.isfile(mytool.lp_file_path) and not mytool.nblp:
            self.report({"ERROR"})

        light_sources = bpy.context.scene.objects.get("light_sources")
        if not light_sources:
            light_sources = bpy.data.objects.new(name = "light_sources", object_data = None)
            scene.collection.objects.link(light_sources)

        for idx in range(0, len(mytool.light_positions)):
            lightData = bpy.data.lights.new(name="RTI_light"+str(idx), type="SPOT")
            lightData.spot_size = 0.1

            current_light = bpy.data.objects.new(name="Light_{0}".format(idx), object_data=lightData)

            (x,y,z) = mytool.light_positions[idx]
            current_light.location = (x, y, z)

            scene.collection.objects.link(current_light)

            current_light.rotation_mode = 'QUATERNION'
            current_light.rotation_quaternion = Vector((x,y,z)).to_track_quat('Z','Y')

            current_light.parent = light_sources

            mytool.light_list.append(current_light.name)

        return {"FINISHED"}

class createCamera(Operator):
    bl_idname = "rti.create_camera"
    bl_label = "Create camera"
    def execute(self, context):
        scene = context.scene
        cameras_obj = [cam for cam in bpy.data.objects if cam.type == 'CAMERA']
        scene.camera_panel.camera.clear()
        if len(cameras_obj) != 0:
            # self.report({'ERROR'}, "Camera already exist in scene.")
            # return {'FINISHED'}
            for cam in bpy.data.cameras:
                cam.animation_data_clear()
                bpy.data.cameras.remove(cam)

        camera_data = bpy.data.cameras.new("Camera")
        camera_data.dof.use_dof = True
        camera_data.type="PERSP"
        camera_object = bpy.data.objects.new("Camera", camera_data)

        # Link camera to current scene
        scene.collection.objects.link(camera_object)

        # Move camera to default location at top of dome
        camera_object.location = (0,0,scene.light_panel.dome_radius)
        scene.camera_panel.camera.append(camera_object.name)

        return {'FINISHED'}

class importSurface(Operator):
    bl_idname = "rti.import_surface"
    bl_label = "Import surface"
    def execute(self, context):
        scene = context.scene
        surfacetool = scene.surface_panel
        objects = [obj for obj in bpy.data.objects if obj.type != 'CAMERA' and obj.type !="SUN"]

        if len(objects) != 0:
            self.report({'ERROR'}, "Surface already exist in scene. Delete the old surface to add new surface")
            return {'FINISHED'}

        if scene.surface_panel.mesh_file_path.endswith(".OBJ") or scene.surface_panel.mesh_file_path.endswith(".obj"):
            bpy.ops.import_scene.obj(filepath=scene.surface_panel.mesh_file_path)
            ob = bpy.context.selected_objects[0]
            # ob.rotation_euler[0] = 0.0523599
            surfacetool.surface.append(ob.name)

        return {'FINISHED'}

class addTexture(Operator):
    bl_idname = "rti.add_texture"
    bl_label = "Add texture"
    def execute(self, context):
        scene = context.scene
        mat = scene.objects[scene.surface_panel.surface[0]].active_material
        nodes = mat.node_tree.nodes
        nodes.clear()
        node_principled = nodes.new(type='ShaderNodeBsdfPrincipled')
        node_principled.location = 0,0
        node_tex = nodes.new('ShaderNodeTexImage')
        node_tex.image = bpy.data.images.load(scene.surface_panel.texture_image_path)
        node_tex.location = -400,0

        node_output = nodes.new(type='ShaderNodeOutputMaterial')
        node_output.location = 400,0

        links = mat.node_tree.links
        link = links.new(node_tex.outputs["Color"], node_principled.inputs["Base Color"])
        link = links.new(node_principled.outputs["BSDF"], node_output.inputs["Surface"])

        node_tex = nodes.new('ShaderNodeTexImage')
        node_tex.location = -400,0
        img = bpy.data.images.get(scene.surface_panel.texture_image_path)
        if img:
            node_tex.image = img

        return {'FINISHED'}

class SetAnimation(Operator):
    bl_idname = "rti.set_animation"
    bl_label = "Create animation"

    def execute(self, context):
        scene = context.scene

        # bpy.
        scene.timeline_markers.clear()
        scene.animation_data_clear()

        if len(bpy.data.worlds)>0:
            bpy.data.worlds.remove(bpy.data.worlds["World"], do_unlink=True)

        data = bpy.data.objects[context.scene.camera_panel.camera[0]].data
        if context.scene.camera_panel.subject is not None:
            data.dof.focus_object = context.scene.camera_panel.subject

        numLights = len(scene.light_panel.light_list)

        print("Setting new animation with",numLights)

        for i in range(0, numLights):
            current_light = bpy.data.objects[scene.light_panel.light_list[i]]
            current_light.data.energy = 0.0

        for k in range(0, numLights):
            for i in range(0, numLights):
                current_light = bpy.data.objects[scene.light_panel.light_list[i]]
                if i != k:
                    current_light.data.energy = 0.0
                    current_light.data.keyframe_insert(data_path="energy", frame=k+1)
                else:
                    current_light.data.energy = scene.light_panel.light_strength
                    current_light.data.keyframe_insert(data_path="energy", frame=k+1)

        return {'FINISHED'}

class acquire(Operator):
    bl_idname = "rti.acquire"
    bl_label = "Acquire"
    bl_use_preview = True

    def execute(self, context):
        global updated_lps
        bpy.context.scene.render.engine = 'CYCLES'
        bpy.context.scene.cycles.device = 'GPU'
        bpy.context.scene.cycles.preview_samples = 100
        bpy.context.scene.cycles.samples = 1000
        bpy.context.scene.cycles.use_preview_denoising = True
        bpy.context.scene.cycles.use_denoising = True
        bpy.context.scene.render.resolution_x = int(1920*context.scene.camera_panel.resolution_factor)
        bpy.context.scene.render.resolution_y = int(context.scene.camera_panel.aspect_ratio*bpy.context.scene.render.resolution_x)
        bpy.context.scene.frame_start = 1
        bpy.context.scene.render.filepath = context.scene.acquisition_panel.output_path
        bpy.context.scene.render.image_settings.color_mode = 'BW'
        bpy.context.scene.render.image_settings.color_depth = '8'
        bpy.context.scene.render.use_overwrite = True
        context.scene.camera = context.scene.objects[context.scene.camera_panel.camera[0]]

        if not context.scene.light_panel.nblp:
            bpy.context.scene.frame_end = len(context.scene.light_panel.light_list)
            bpy.ops.rti.set_animation()
            bpy.ops.render.render(animation=True, use_viewport = True, write_still=True)
            bpy.ops.render.play_rendered_anim()

        else:
            print("Executing NBLP")
            nblp = Nblp_2(context=context)

            # updated_lps = nblp.iterations[0].lps_cartesian
            # nblp.generate_lp_file(iteration_nb=0, file_path=context.scene.acquisition_panel.output_path+"iteration_"+str(len(nblp.iterations)-1)+".lp")
            # bpy.context.scene.render.filepath = context.scene.acquisition_panel.output_path+nblp.iterations[0].filenames_subtext+"#.png"
            # bpy.ops.rti.create_lights()
            # bpy.context.scene.frame_end = len(context.scene.light_panel.light_list)
            # bpy.ops.rti.set_animation()
            # bpy.ops.render.render(animation=True, use_viewport = True, write_still=True)
            # bpy.ops.render.play_rendered_anim()
            # nblp.iterations[0].plot_lps().savefig(context.scene.acquisition_panel.output_path+"30.png")
            # nblp.iterations[0].rename_files(context.scene.acquisition_panel.output_path)
            # nblp.calculate_entropies(len(nblp.iterations)-1,context.scene.acquisition_panel.output_path+"\\entropies\\")

            # nblp.dense_acquisition()
            # updated_lps = nblp.iterations[len(nblp.iterations)-1].lps_cartesian
            # nblp.generate_lp_file(iteration_nb=1, file_path=context.scene.acquisition_panel.output_path+"iteration_"+str(len(nblp.iterations)-1)+".lp")
            # bpy.context.scene.render.filepath = context.scene.acquisition_panel.output_path+"dense_acquisition\\"+nblp.iterations[len(nblp.iterations)-1].filenames_subtext+"#.png"
            # bpy.ops.rti.create_lights()
            # bpy.context.scene.frame_end = len(context.scene.light_panel.light_list)
            # bpy.ops.rti.set_animation()
            # bpy.ops.render.render(animation=True, use_viewport = True, write_still=True)
            # bpy.ops.render.play_rendered_anim()
            # nblp.iterations[len(nblp.iterations)-1].plot_lps().savefig(context.scene.acquisition_panel.output_path+"dense_acquisition\\30.png")
            # nblp.iterations[len(nblp.iterations)-1].rename_files(context.scene.acquisition_panel.output_path+"dense_acquisition\\")

            nblp.write_log(context.scene.acquisition_panel.output_path+"log.yaml")

        return {'FINISHED'}

#################################### Operators } ####################################
#################################### Panels { #######################################
class rti_panel(Panel):

    bl_label = "Surface Adaptive RTI"
    bl_idname = "VIEW3D_PT_surface_adaptive_rti"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Surface Adaptive RTI"

    def draw(self, context):
        layout = self.layout
        scene = context.scene

class light_panel(Panel):
    bl_label = "Light positions"
    bl_parent_id = "VIEW3D_PT_surface_adaptive_rti"
    bl_idname = "VIEW3D_PT_light_panel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Surface Adaptive RTI"

    def draw(self, context):
        global updated_lps
        layout = self.layout
        scene = context.scene
        lighttool = scene.light_panel
        layout.label(text="Light positions")
        layout.prop(lighttool, "nblp")
        layout.prop(lighttool, "light_strength")
        layout.prop(lighttool, "dome_radius")

        if not lighttool.nblp:
            layout.prop(lighttool,"lp_file_path")
            row = layout.row(align = True)
            updated_lps = read_lp_file(lighttool.lp_file_path, lighttool.dome_radius)
            row = layout.row(align = True)
            row.operator("rti.create_lights")
        else:
            for current_light in bpy.data.lights:
                bpy.data.lights.remove(current_light)

class surface_panel(Panel):
    bl_label = "Surface"
    bl_parent_id = "VIEW3D_PT_surface_adaptive_rti"
    bl_idname = "VIEW3D_PT_surface_panel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Surface Adaptive RTI"
    def draw(self, context):
        layout = self.layout
        scene = context.scene
        surfacetool = scene.surface_panel
        layout.label(text="Surface")
        layout.prop(surfacetool, "mesh_file_path")
        row = layout.row(align = True)
        row.operator("rti.import_surface")
        layout.label(text="Texture")
        layout.prop(surfacetool, "texture_image_path")
        row = layout.row(align = True)
        row.operator("rti.add_texture")
        layout.prop(surfacetool, "metallic", slider=True)
        layout.prop(surfacetool, "roughness", slider=True)
        layout.prop(surfacetool, "specularity", slider=True)

class camera_panel(Panel):
    bl_label = "Camera"
    bl_parent_id = "VIEW3D_PT_surface_adaptive_rti"
    bl_idname = "VIEW3D_PT_camera_panel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Surface Adaptive RTI"
    def draw(self, context):
        layout = self.layout
        scene = context.scene
        cameratool = scene.camera_panel
        layout.label(text="Camera")
        layout.prop(cameratool, "camera_height", slider=True)
        layout.prop(cameratool, "aspect_ratio", slider=True)
        layout.prop(cameratool, "focal_length", slider=True)
        layout.prop(cameratool, "subject")
        layout.prop(cameratool, "resolution_factor")
        layout.prop(cameratool, "view_port_size", slider=True)
        layout.prop(cameratool, "sensor_size", slider=True)
        row = layout.row(align = True)
        row.operator("rti.create_camera")

class acquisition_panel(Panel):
    bl_label = "Acquisition"
    bl_parent_id = "VIEW3D_PT_surface_adaptive_rti"
    bl_idname = "VIEW3D_PT_acquisition_panel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Surface Adaptive RTI"
    def draw(self, context):
        layout = self.layout
        scene = context.scene
        layout.label(text="Acquisition")
        layout.prop(scene.acquisition_panel, "output_path")
        # layout.operator("rti.set_animation")
        layout.operator("rti.acquire")
        row = layout.row(align = True)
        row.operator("rti.reset_scene")

#################################### Panels } #######################################

#################################### Register classes { #######################################
classes = (reset_scene,
            light,
            camera,
            surface,
            lightSettings,
            cameraSettings,
            surfaceSettings,
            acquisitionSettings,
            createLights,
            createCamera,
            importSurface,
            addTexture,
            SetAnimation,
            acquire,
            rti_panel,
            surface_panel,
            light_panel,
            camera_panel,
            acquisition_panel)

def register():
    for cls in classes:
        bpy.utils.register_class(cls)

    bpy.types.Scene.light_panel = PointerProperty(type=lightSettings)
    bpy.types.Scene.surface_panel = PointerProperty(type=surfaceSettings)
    bpy.types.Scene.camera_panel = PointerProperty(type=cameraSettings)
    bpy.types.Scene.acquisition_panel = PointerProperty(type=acquisitionSettings)

def unregister():
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)
    del bpy.types.Scene.light_panel

if __name__ == "__main__":
    register()

#################################### Register classes } #######################################


from gwibber.microblog import network, util
import cgi
from oauth import oauth
from gwibber.microblog.util import resources
from gettext import lgettext as _

import logging
logger = logging.getLogger("Twitter")
logger.debug("Initializing.")

PROTOCOL_INFO = {
  "name": "Twitter",
  "version": "1.0",

  "config": [
    "private:secret_token",
    "access_token",
    "username",
    "color",
    "receive_enabled",
    "send_enabled",
  ],

  "authtype": "oauth1a",
  "color": "#729FCF",

  "features": [
    "send",
    "receive",
    "search",
    "tag",
    "reply",
    "responses",
    "private",
    "public",
    "delete",
    "follow",
    "unfollow",
    "profile",
    "retweet",
    "like",
    "send_thread",
    "send_private",
    "user_messages",
    "sinceid",
    "lists",
    "list",
  ],

  "default_streams": [
    "receive",
    "images",
    "responses",
    "private",
    "lists",
  ],
}

URL_PREFIX = "https://twitter.com"
API_PREFIX = "https://api.twitter.com/1"

class Client ():
  def __init__(self, acct):
    self.service = util.getbus("Service")
    if acct.has_key("secret_token") and acct.has_key("password"): acct.pop("password")
    self.account = acct

    if not acct.has_key("access_token") and not acct.has_key("secret_token"):
      return [{"error": {"type": "auth", "account": self.account, "message": _("Failed to find credentials")}}]

    self.sigmethod = oauth.OAuthSignatureMethod_HMAC_SHA1()
    self.consumer = oauth.OAuthConsumer(*util.resources.get_twitter_keys())
    self.token = oauth.OAuthToken(acct["access_token"], acct["secret_token"])

  def _common(self, data):
    m = {}
    try:
      m["mid"] = str(data["id"])
      m["service"] = "twitter"
      m["account"] = self.account["id"]
      if data.has_key("created_at"):
        m["time"] = util.parsetime(data["created_at"])
      m["text"] = util.unescape(data["text"])
      m["text"] = cgi.escape(m["text"])
      m["content"] = m["text"]

      # Go through the entities in the tweet and use them to linkify/filter tweeks as appropriate
      if data.has_key("entities"):

        #Get mention entries
        if data["entities"].has_key("user_mentions"):
          names = []
          for mention in data["entities"]["user_mentions"]:
            if not mention["screen_name"] in names:
              try:
                m["content"] = m["content"].replace("@" + mention["screen_name"], "@" + mention["screen_name"] + "")
              except:
                pass
            names.append(mention["screen_name"])

        #Get hashtag entities
        if data["entities"].has_key("hashtags"):
          hashtags = []
          for tag in data["entities"]["hashtags"]:
            if not tag["text"] in hashtags:
              try:
                m["content"] = m["content"].replace("#" + tag["text"], "#" + tag["text"] + "")
              except:
                pass
            hashtags.append(tag["text"])

        # Get url entities - These usually go in the link stream, but if they're picturesor videos, they should go in the proper stream
        if data["entities"].has_key("urls"):
          for urls in data["entities"]["urls"]:
              url = cgi.escape (urls["url"])
              expanded_url = url
              if urls.has_key("expanded_url"):
                if not urls["expanded_url"] is None:
                  expanded_url = cgi.escape(urls["expanded_url"])

              display_url  = url
              if urls.has_key("display_url"):
                display_url = cgi.escape (urls["display_url"])

              if url == m["content"]:
                m["content"] = "" + display_url + ""
              else:
                try:
                  startindex = m["content"].index(url)
                  endindex   = startindex + len(url)
                  start      = m["content"][0:startindex]
                  end        = m["content"][endindex:]
                  m["content"] = start + "" + display_url + "" + end
                except:
                  logger.debug ("Failed to set url for ID: %s",  m["mid"])

              m["type"] = "link"

              images = util.imgpreview(expanded_url)
              videos = util.videopreview(expanded_url)
              if images:
                m["images"] = images
                m["type"] = "photo"
              elif videos:
                m["images"] = videos
                m["type"] = "video"
              else:
                # Well, it's not anything else, so it must be a link
                m["link"] = {}
                m["link"]["picture"] = ""
                m["link"]["name"] = ""
                m["link"]["description"] = m["content"]
                m["link"]["url"] = url
                m["link"]["icon"] = ""
                m["link"]["caption"] = ""
                m["link"]["properties"] = {}

        if data["entities"].has_key("media"):
          for media in data["entities"]["media"]:
            try:
              url = cgi.escape (media["url"])
              media_url_https = media["media_url_https"]
              expanded_url = url
              if media.has_key("expanded_url"):
                expanded_url = cgi.escape(media["expanded_url"])

              display_url  = url
              if media.has_key("display_url"):
                display_url = cgi.escape (media["display_url"])

              startindex = m["content"].index(url)
              endindex   = startindex + len(url)
              start      = m["content"][0:startindex]
              end        = m["content"][endindex:]
              m["content"] = start + "" + display_url + "" + end

              if media["type"] == "photo":
                m["type"] = "photo"
                m["photo"] = {}
                m["photo"]["picture"] = media_url_https
                m["photo"]["url"] = None
                m["photo"]["name"] = None

            except:
              pass

      else:
        m["content"] = util.linkify(util.unescape(m["text"]),
          ((util.PARSE_HASH, '#\\1' % m["account"]),
          (util.PARSE_NICK, '@\\1' % m["account"])), escape=True)

      m["html"] = m["content"]

      m["to_me"] = ("@%s" % self.account["username"]) in data["text"]   # Check if it's a reply directed at the user
      m["favorited"] = data.get("favorited", False)                     # Check if the tweet has been favourited

    except:
      logger.error("%s failure - %s", PROTOCOL_INFO["name"], data)
      return {}

    return m

  def _user(self, user):
    return {
        "name": user.get("name", None),
        "nick": user.get("screen_name", None),
        "id": user.get("id", None),
        "location": user.get("location", None),
        "followers": user.get("followers_count", None),
        "friends": user.get("friends_count", None),
        "description": user.get("description", None),
        "following": user.get("following", None),
        "protected": user.get("protected", None),
        "statuses": user.get("statuses_count", None),
        "image": user.get("profile_image_url", None),
        "website": user.get("url", None),
        "url": "/".join((URL_PREFIX, user.get("screen_name", ""))) or None,
        "is_me": user.get("screen_name", None) == self.account["username"],
    }

  def _message(self, data):
    if type(data) != dict:
      logger.error("Cannot parse message data: %s", str(data))
      return {}

    n = {}
    if data.has_key("retweeted_status"):
      n["retweeted_by"] = self._user(data["user"] if "user" in data else data["sender"])
      if data.has_key("created_at"):
        n["time"] = util.parsetime(data["created_at"])
      data = data["retweeted_status"]
    else:
      n["retweeted_by"] = None
      if data.has_key("created_at"):
        n["time"] = util.parsetime(data["created_at"])

    m = self._common(data)
    for k in n:
      m[k] = n[k]

    m["source"] = data.get("source", False)

    if data.has_key("in_reply_to_status_id"):
      if data["in_reply_to_status_id"]:
        m["reply"] = {}
        m["reply"]["id"] = data["in_reply_to_status_id"]
        m["reply"]["nick"] = data["in_reply_to_screen_name"]
        if m["reply"]["id"] and m["reply"]["nick"]:
          m["reply"]["url"] = "/".join((URL_PREFIX, m["reply"]["nick"], "statuses", str(m["reply"]["id"])))
        else:
          m["reply"]["url"] = None

    m["sender"] = self._user(data["user"] if "user" in data else data["sender"])
    m["url"] = "/".join((m["sender"]["url"], "statuses", str(m.get("mid", None))))

    return m

  def _responses(self, data):
    m = self._message(data)
    m["type"] = None

    return m

  def _private(self, data):
    m = self._message(data)
    m["private"] = True
    m["type"] = None

    m["recipient"] = {}
    m["recipient"]["name"] = data["recipient"]["name"]
    m["recipient"]["nick"] = data["recipient"]["screen_name"]
    m["recipient"]["id"] = data["recipient"]["id"]
    m["recipient"]["image"] = data["recipient"]["profile_image_url"]
    m["recipient"]["location"] = data["recipient"]["location"]
    m["recipient"]["url"] = "/".join((URL_PREFIX, m["recipient"]["nick"]))
    m["recipient"]["is_me"] = m["recipient"]["nick"] == self.account["username"]
    m["to_me"] = m["recipient"]["is_me"]

    return m

  def _result(self, data):
    m = self._common(data)

    if data["to_user_id"]:
      m["reply"] = {}
      m["reply"]["id"] = data["to_user_id"]
      m["reply"]["nick"] = data["to_user"]

    m["sender"] = {}
    m["sender"]["nick"] = data["from_user"]
    m["sender"]["id"] = data["from_user_id"]
    m["sender"]["image"] = data["profile_image_url"]
    m["sender"]["url"] = "/".join((URL_PREFIX, m["sender"]["nick"]))
    m["sender"]["is_me"] = m["sender"]["nick"] == self.account["username"]
    m["url"] = "/".join((m["sender"]["url"], "statuses", str(m["mid"])))
    return m

  def _profile(self, data):
    if "error" in data:
      return {
          "error": data["error"]
        }
    return {
        "name": data.get("name", data["screen_name"]),
        "service": "twitter",
        "stream": "profile",
        "account": self.account["id"],
        "mid": data["id"],
        "text": data.get("description", ""),
        "nick": data["screen_name"],
        "url": data.get("url", ""),
        "protected": data.get("protected", False),
        "statuses": data.get("statuses_count", 0),
        "followers": data.get("followers_count", 0),
        "friends": data.get("friends_count", 0),
        "following": data.get("following", 0),
        "favourites": data.get("favourites_count", 0),
        "image": data["profile_image_url"],
        "utc_offset": data.get("utc_offset", 0),
        "id": data["id"],
        "lang": data.get("lang", "en"),
        "verified": data.get("verified", False),
        "geo_enabled": data.get("geo_enabled", False),
        "time_zone": data.get("time_zone", "")
    }

  def _list(self, data):
    return {
        "mid": data["id"],
        "service": "twitter",
        "account": self.account["id"],
        "time": 0,
        "text": data["description"],
        "html": data["description"],
        "content": data["description"],
        "url": "/".join((URL_PREFIX, data["uri"])),
        "sender": self._user(data["user"]),
        "name": data["name"],
        "nick": data["slug"],
        "key": data["slug"],
        "full": data["full_name"],
        "uri": data["uri"],
        "mode": data["mode"],
        "members": data["member_count"],
        "followers": data["subscriber_count"],
        "kind": "list",
    }

  def _get(self, path, parse="message", post=False, single=False, **args):
    url = "/".join((API_PREFIX, path))

    request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, self.token,
        http_method=post and "POST" or "GET", http_url=url, parameters=util.compact(args))
    request.sign_request(self.sigmethod, self.consumer, self.token)

    if post:
      headers = request.to_header()
      data = network.Download(url, util.compact(args), post, header=headers).get_json()
    else:
      data = network.Download(request.to_url(), None, post).get_json()
    resources.dump(self.account["service"], self.account["id"], data)

    if isinstance(data, dict) and data.get("errors", 0):
      if "authenticate" in data["errors"][0]["message"]:
        logstr = """%s: %s - %s""" % (PROTOCOL_INFO["name"], _("Authentication failed"), data["errors"][0]["message"])
        logger.error("%s", logstr)
        return [{"error": {"type": "auth", "account": self.account, "message": data["errors"][0]["message"]}}]
      else:
        for error in data["errors"]:
          logstr = """%s: %s - %s""" % (PROTOCOL_INFO["name"], _("Unknown failure"), error["message"])
          return [{"error": {"type": "unknown", "account": self.account, "message": error["message"]}}]
    elif isinstance(data, dict) and data.get("error", 0):
      if "Incorrect signature" in data["error"]:
        logstr = """%s: %s - %s""" % (PROTOCOL_INFO["name"], _("Request failed"), data["error"])
        logger.error("%s", logstr)
        return [{"error": {"type": "auth", "account": self.account, "message": data["error"]}}]
    elif isinstance(data, str):
      logstr = """%s: %s - %s""" % (PROTOCOL_INFO["name"], _("Request failed"), data)
      logger.error("%s", logstr)
      return [{"error": {"type": "request", "account": self.account, "message": data}}]

    if parse == "follow" or parse == "unfollow":
      if isinstance(data, dict) and data.get("error", 0):
        logstr = """%s: %s - %s""" % (PROTOCOL_INFO["name"], _("%s failed" % parse), data["error"])
        logger.error("%s", logstr)
        return [{"error": {"type": "auth", "account": self.account, "message": data["error"]}}]
      else:
        return [["friendships", {"type": parse, "account": self.account["id"], "service": self.account["service"],"user_id": data["id"], "nick": data["screen_name"]}]]

    if parse == "profile" and isinstance(data, dict):
      return self._profile(data)

    if parse == "list":
      return [self._list(l) for l in data["lists"]]

    if single: return [getattr(self, "_%s" % parse)(data)]
    if parse: return [getattr(self, "_%s" % parse)(m) for m in data]
    else: return []

  def _search(self, **args):
    data = network.Download("http://search.twitter.com/search.json", util.compact(args))
    data = data.get_json()["results"]

    if type(data) != list:
      logger.error("Cannot parse search data: %s", str(data))
      return []

    return [self._result(m) for m in data]

  def __call__(self, opname, **args):
    return getattr(self, opname)(**args)

  def receive(self, count=util.COUNT, since=None):
    return self._get("statuses/home_timeline.json", include_entities=1, count=count, since_id=since)

  def responses(self, count=util.COUNT, since=None):
    return self._get("statuses/mentions.json", "responses", include_entities=1, count=count, since_id=since)

  def private(self, count=util.COUNT, since=None):
    private = self._get("direct_messages.json", "private", include_entities=1, count=count, since_id=since) or []
    private_sent = self._get("direct_messages/sent.json", "private", count=count, since_id=since) or []
    return private + private_sent

  def public(self):
    return self._get("statuses/public_timeline.json", include_entities=1)

  def lists(self, **args):
    following = self._get("%s/lists/subscriptions.json" % self.account["username"], "list") or []
    lists = self._get("%s/lists.json" % self.account["username"], "list") or []
    return following + lists

  def list(self, user, id, count=util.COUNT, since=None):
    return self._get("%s/lists/%s/statuses.json" % (user, id), include_entities=1, per_page=count, since_id=since)

  def search(self, query, count=util.COUNT, since=None):
    return self._search(include_entities=1, q=query, rpp=count, since_id=since)

  def tag(self, query, count=util.COUNT, since=None):
    return self._search(q="#%s" % query, count=count, since_id=since)

  def delete(self, message):
    return self._get("statuses/destroy/%s.json" % message["mid"], None, post=True, do=1)

  def like(self, message):
    return self._get("favorites/create/%s.json" % message["mid"], None, post=True, do=1)

  def send(self, message):
    return self._get("statuses/update.json", post=True, single=True,
        status=message)

  def send_private(self, message, private):
    return self._get("direct_messages/new.json", "private", post=True, single=True,
        text=message, screen_name=private["sender"]["nick"])

  def send_thread(self, message, target):
    return self._get("statuses/update.json", post=True, single=True,
        status=message, in_reply_to_status_id=target["mid"])

  def retweet(self, message):
    return self._get("statuses/retweet/%s.json" % message["mid"], None, post=True, do=1)

  def follow(self, screen_name):
    return self._get("friendships/create.json", screen_name=screen_name, post=True, parse="follow")

  def unfollow(self, screen_name):
    return self._get("friendships/destroy.json", screen_name=screen_name, post=True, parse="unfollow")

  def profile(self, id=None, count=None, since=None):
    return self._get("users/show.json", screen_name=id, count=count, since_id=since, parse="profile")

  def user_messages(self, id=None, count=util.COUNT, since=None):
    profiles = [self.profile(id)] or []
    messages = self._get("statuses/user_timeline.json", id=id, include_entities=1, count=count, since_id=since) or []
    return messages + profiles


import glob, os
import cv2
import numpy as np

def red_ch_zeros(foldername):
    '''
    Drop red channel.
    :param foldername: string
    :return: None
    '''
    dir_name = foldername + os.sep + "*"
    image_files_list = list(glob.glob(dir_name))

    for image_file in image_files_list:
        src = cv2.imread(image_file, cv2.IMREAD_UNCHANGED)
        src[:, :, 2] = np.zeros([src.shape[0], src.shape[1]])
        # save image
        cv2.imwrite(image_file, src)

    print("모든 이미지에 대하여 Red channel Zeros 를 적용하였습니다.")


import time
from typing import List

class Solution:
    '''
    3.Longest Substring Without Repeating Characters
    Given a string s, find the length of the longest substring without repeating characters.
    '''
    def basic(self, s):
        n = len(s)
        j = -1
        mp = {}
        res = 0
        for i in range(n):
            cur = s[i]
            if cur in mp:
                j = max(j, mp[cur])
            res = max(i - j, res)
            mp[cur] = i

        return res

    def lengthOfLongestSubstring(self, s: str) -> int:
        '''
        i is the fast pointer,
        '''

        i = j = length = 0
        longest = [0]*128

        while i < len(s):
            current_char = s[i]
            longest[ord(current_char)] += 1

            while longest[ord(current_char)] > 1:
                longest[ord(s[j])] -= 1
                j += 1

            length = max(length, i - j + 1)
            i += 1

        return length

    def lengthOfLongestSubstring_hashmap(self, s: str) -> int:
        n = len(s)
        ans = j = 0
        mp = {}

        # j is the last time when the current char appears
        # mp stores the position the key char appears last time

        for i in range(n):
            if s[i] in mp:
                j = max(mp[s[i]], j)

            ans = max(ans, i - j)
            mp[s[i]] = i

        return ans

    '''
    30. Substring with Concatenation of All Words
    You are given a string s and an array of strings words of the same length.
    Return all starting indices of substring(s) in s that is a concatenation of each word
    in words exactly once, in any order, and without any intervening characters.
    You can return the answer in any order.

    Input: s = "barfoothefoobarman", words = ["foo","bar"]
    Output: [0,9]

    '''
    def substringWithConcatenationofAllWords(self, s: str, words: List[str]) -> List[int]:

        result = [0, 0]
        ans = 0
        mp = {}

        for word in words:
            mp[word] = -1

        l = len(words[0])
        i = j = 0

        while (i < len(s)):
            word = s[i:i+l]
            if(word in mp and mp[word] > 0):
                j = max(mp[word], j)

            if(i+l-j > ans):
                result = [j, i+l]
                ans = i+l-j

            mp[word] = i+l
            i = i+l

        return result

    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        '''
        209
        '''
        n = len(nums)
        j = 0
        temp_sum = 0
        res = float("inf")

        for i in range(n):
            cur = nums[i]
            temp_sum += cur
            while temp_sum >= target:
                res = min(i - j + 1, res)
                temp_sum -= nums[j]
                j += 1
        if res == float("inf"):
            return 0
        else:
            return res

if __name__ == "__main__":
    solution = Solution()
    start_time = time.time()
    # print(solution.lengthOfLongestSubstring_hashmap("abcbde"))
    print(solution.substringWithConcatenationofAllWords("barfoofoobarthefoobarman", ["bar","foo","the"]))
    print(time.time() - start_time)

    while True:
        text = input(">>")
        print(solution.lengthOfLongestSubstring(text))