
from traitlets import Bunch
import ipywidgets
from ipywidgets import (
    IntText,
    BoundedIntText,
    Layout,
    Button,
    Label,
    Box, HBox,
    # Box应当始终假定display不明
    # HBox/VBox应当仅用于【单行/单列】内容
)
from .utils import collect_local_module_names

model_name_list = [
    "Linaqruf/anything-v3.0",
    "MoososCap/NOVEL-MODEL", 
    "Baitian/momocha",
    "Baitian/momoco",
    "hequanshaguo/monoko-e",
    "ruisi/anything",
    "hakurei/waifu-diffusion-v1-3", 
    "CompVis/stable-diffusion-v1-4", 
    "runwayml/stable-diffusion-v1-5", 
    "stabilityai/stable-diffusion-2",
    "stabilityai/stable-diffusion-2-base",
    "hakurei/waifu-diffusion", 
    "naclbit/trinart_stable_diffusion_v2_60k", 
    "naclbit/trinart_stable_diffusion_v2_95k", 
    "naclbit/trinart_stable_diffusion_v2_115k", 
    "ringhyacinth/nail-set-diffuser",
    "Deltaadams/Hentai-Diffusion",
    "BAAI/AltDiffusion",
    "BAAI/AltDiffusion-m9",
    "IDEA-CCNL/Taiyi-Stable-Diffusion-1B-Chinese-v0.1",
    "IDEA-CCNL/Taiyi-Stable-Diffusion-1B-Chinese-EN-v0.1",
    "huawei-noah/Wukong-Huahua"]

samler_list = [
    "default",
    "DPMSolver",
    "EulerDiscrete",
    "EulerAncestralDiscrete",
    "PNDM",
    "DDIM",
    "LMSDiscrete",
    "HeunDiscrete",
    "KDPM2AncestralDiscrete",
    "KDPM2Discrete"
]
_DefaultLayout = {
    'col04': {
        'flex':  "4 4 30%",
        'min_width':  "6rem",    #480/ sm-576, md768, lg-992, xl-12000
        'max_width':  "calc(100% - 0.75rem)",
        'margin':  "0.375rem",
        'align_items':  "center"
    },
    'col06': {
        'flex':  "6 6 45%",
        'min_width':  "9rem",   #手机9rem会换行
        'max_width':  "calc(100% - 0.75rem)",
        'margin':  "0.375rem",
        'align_items':  "center"
    },
    'col08': {
        'flex':  "8 8 60%",
        'min_width':  "12rem",
        'max_width':  "calc(100% - 0.75rem)",
        'margin':  "0.375rem",
        'align_items':  "center"
    },
    'col12': {
        'flex':  "12 12 90%",
        'max_width':  "calc(100% - 0.75rem)",
        'margin':  "0.375rem",
        'align_items':  "center"
    },
    'btnV5': {}, #见css
}

# 为工具设置布局，并标记dom class
def setLayout(layout_name, widget):
    _lists = layout_name if isinstance(layout_name, list) else [layout_name]
    
    for name in _lists:
        if layout_name not in _DefaultLayout: 
            raise Exception(f'未定义的layout名称：{layout_name}')
        
        styles = _DefaultLayout[layout_name];
        
        for key in styles:
            setattr(widget.layout, key, styles[key])
        
        widget.add_class(layout_name)
    
_description_style = { 'description_width': "4rem" }
_Views = {
    # Textarea 
    "prompt": {
        "__type": 'Textarea',
        "class_name": 'prompt',
        "layout": {
            "flex": '1',
            "min_height": '10rem',
            "max_width": 'calc(100% - 0.75rem)',
            "margin": '0.375rem',
            "align_items": 'stretch'
        },
        "style": _description_style,
        "description": '正面描述' ,
        "description_tooltip": '仅支持(xxx)、(xxx:1.2)、[xxx]三种语法。设置括号格式可以对{}进行转换。',
    },
    "negative_prompt": {
        "__type": 'Textarea',
        "class_name": 'negative_prompt',
        "layout": {
            "flex": '1',
            "max_width": 'calc(100% - 0.75rem)',
            "margin": '0.375rem',
            "align_items": 'stretch'
        },
        "style": _description_style,
        "description": '负面描述',
        "description_tooltip": '使生成图像的内容远离负面描述的文本',
    },
    
    # Text
    "concepts_library_dir": {
        "__type": 'Text',
        "class_name": 'concepts_library_dir',
        "layout_name": 'col08',
        "style": _description_style,
        "description": '风格权重',
        "description_tooltip": 'TextualInversion训练的、“风格”或“人物”的权重文件路径',
        "value": 'outputs/textual_inversion',
    },
    "output_dir": {
        "__type": 'Text',
        "class_name": 'output_dir',
        "layout_name": 'col08',
        "style": _description_style,
        "description": '保存路径',
        "description_tooltip": '用于保存输出图片的路径',
        "value": 'outputs',
    },
    "seed": {
        "__type": 'IntText',
        "class_name": 'seed',
        "layout_name": 'col04',
        "style": _description_style,
        "description": '随机种子',
        "description_tooltip": '-1表示随机生成。',
        "value": -1,
    },
    "num_inference_steps": {
        "__type": 'BoundedIntText',
        "class_name": 'num_inference_steps',
        "layout_name": 'col04',
        "style": _description_style,
        "description": '推理步数',
        "description_tooltip": '推理步数（Step）：生成图片的迭代次数，步数越多运算次数越多。',
        "value": 50,
        "min": 2,
        "max": 10000,
    },
    "num_return_images": {
        "__type": 'BoundedIntText',
        "class_name": 'num_return_images',
        "layout_name": 'col04',
        "style": _description_style,
        "description": '生成数量',
        "description_tooltip": '生成图片的数量',
        "value": 1,
        "min": 1,
        "max": 100,
        "step": 1,
    },
    "guidance_scale": {
        "__type": 'BoundedFloatText',
        "class_name": 'guidance_scale',
        "layout_name": 'col04',
        "style": _description_style,
        "description": 'CFG',
        "description_tooltip": '引导度（CFG Scale）：控制图片与描述词之间的相关程度。',
        "min": 0,
        "max": 50,
        "value": 7.5,
    },
    
    # Dropdown 
    "enable_parsing": {
        "__type": 'Dropdown',
        "class_name": 'enable_parsing',
        "layout_name": 'col04',
        "style": _description_style,
        "description": '括号格式',
        "description_tooltip": '增加权重所用括号的格式，可以将{}替换为()。选择“否”则不解析加权语法',
        "value": '圆括号 () 加强权重',
        "options": ['圆括号 () 加强权重','花括号 {} 加权权重', '否'],
    },
    "fp16": {
        "__type": 'Dropdown',
        "class_name": 'fp16',
        "layout_name": 'col04',
        "style": _description_style,
        "description": '算术精度',
        "description_tooltip": '模型推理使用的精度。选择float16可以加快模型的推理速度，但会牺牲部分的模型性能。',
        "value": 'float32',
        "options": ['float32', 'float16'],
    },
    "max_embeddings_multiples": {
        "__type": 'Dropdown',
        "class_name": 'max_embeddings_multiples',
        "layout_name": 'col04',
        "style": _description_style,
        "description": '描述上限',
        "description_tooltip": '修改描述词的上限倍数，使模型能够输入更长更多的描述词。',
        "value": '3',
        "options": ['1','2','3','4','5'],
    },
    "sampler": {
        "__type": 'Dropdown',
        "class_name": 'sampler',
        "layout_name": 'col04',
        "style": _description_style,
        "description": '采样器',
        "value": 'default',
        "options": samler_list, 
    },
    "standard_size": {
        "__type": 'Dropdown',
        "class_name": 'standard_size',
        "layout_name": 'col04',
        "style": _description_style,
        "description": '图片尺寸',
        "description_tooltip": '生成图片的尺寸',
        "value": 5120512,
        "options": [
            ('竖向（512x768）',            5120768),
            ('横向（768x512）',            7680512),
            ('正方形（640x640）',          6400640),
            ('大尺寸-竖向（512x1024）',    5121024),
            ('大尺寸-横向（1024x512）',   10240512),
            ('大尺寸-正方形（1024x1024）',10241024),
            ('小尺寸-竖向（384x640）',     3840640),
            ('小尺寸-横向（640x384）',     6400384),
            ('小尺寸-正方形（512x512）',   5120512),
        ],
    },
    "superres_model_name": {
        "__type": 'Dropdown',
        "class_name": 'superres_model_name',
        "layout_name": 'col04',
        "style": _description_style,
        "description": '图像放大',
        "description_tooltip": '指定放大图片尺寸所用的模型',
        "value": '无',
        "options": ['falsr_a', 'falsr_b', 'falsr_c', '无'],
    },
    
    # Combobox 
    "model_name": {
        "__type": 'Combobox',
        "class_name": 'model_name',
        "layout_name": 'col08',
        "style": _description_style,
        "description": '模型名称',
        "description_tooltip": '需要加载的模型名称',
        "value": 'MoososCap/NOVEL-MODEL',
        "options": model_name_list,
        "ensure_option": False,
    },
    
    # Button
    "run_button": {
        "__type": 'Button',
        "class_name": 'run_button',
        "layout_name": 'btnV5',
        "button_style": 'success', # 'success', 'info', 'warning', 'danger' or ''
        "description": '生成图片！',
        "tooltip": '单击开始生成图片',
        "icon": 'check'
    },
    "collect_button": {
        "__type": 'Button',
        "class_name": 'collect_button',
        "layout_name": 'btnV5',
        "button_style": 'info', # 'success', 'info', 'warning', 'danger' or ''
        "description": '收藏图片',
        "tooltip": '将图片转移到Favorates文件夹中',
        "icon": 'star-o',
        "disabled": True,
    },
    
    # Box
    "box_gui": {
        "__type": 'Box',
        "layout": {
            "display": 'block', #Box默认值为flex
            "margin": '0 45px 0 0',
        },
    },
    "box_main": {
        "__type": 'Box',
        "layout": {
            "display": 'flex',
            "flex_flow": 'row wrap', #HBox会覆写此属性
            "align_items": 'center',
            "max_width": '100%',
        },
    },
    
}

SHARED_STYLE_SHEETS = '''
@media (max-width:576px) {
    {root} {
        margin-right: 0 !important;
    }
    {root} .widget-text,
    {root} .widget-dropdown,
    {root} .widget-hslider,
    {root} .widget-textarea {
        flex-wrap: wrap !important;
        height: auto;
        margin-top: 0.1rem !important;
        margin-bottom: 0.1rem !important;
    }
    {root} .widget-text > label,
    {root} .widget-dropdown > label,
    {root} .widget-hslider > label,
    {root} .widget-textarea > label {
        width: 100% !important;
        text-align: left !important;
        font-size: small !important;
    }
    {root} .col04,
    {root} .col06 {
        /*手机9rem会换行*/
        min-width: 6rem !important; 
    }
}
{root} {
    background-color: var(--jp-layout-color1);
}
{root} .widget-text > label,
{root} .widget-text > .widget-label {
    user-select: none;
}

/* bootcss v5 */
{root} button.btnV5.jupyter-button.widget-button
{
    height:auto;
    font-weight: 400;
    line-height: 1.5;
    text-align: center;
    vertical-align: middle;
    padding: .375rem .75rem;
    font-size: 1rem;
}
{root} button.btnV5.btn-sm.jupyter-button.widget-button
{
    padding: .25rem .5rem;
    font-size: .875rem;
}
{root} .jupyter-widgets.widget-tab > .p-TabBar .p-TabBar-tab {
    padding: .5rem 0;
    text-align: center;
    transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out;
}
'''

CUSTOM_OPTIONS = ('__type','class_name', 'layout_name')
def _mergeViewOptions(defaultOpt,kwargs):
    r = {}
    r.update(defaultOpt)
    
    for k in kwargs:
        r[k] = kwargs[k]
        if (k in defaultOpt) and (type(defaultOpt[k]) == 'dict') \
        and (type(kwargs[k]) == 'dict'):
            r[k] = {}
            r[k].update(defaultOpt[k])
            r[k].update(kwargs[k])
    
    #处理layout
    if ('layout' in r) and (type(r['layout']) == 'dict'):
        r.layout = Layout(**r['layout'])
    
    #提取非ipywidgets参数
    r2 = {}
    for k in CUSTOM_OPTIONS:
        if (k in r):
            r2[k] = r.pop(k)
    
    return (r, r2)

    
def createView(name, value = None, **kwargs):
    assert name in _Views, f'未定义的View名称 {name}'
    assert '__type' in _Views[name], f'View {name} 没有声明组件类型'
    
    # 合并参数
    args, options = _mergeViewOptions(_Views[name], kwargs)
   
    # 反射
    __type = _Views[name]['__type']
    assert hasattr(ipywidgets, __type), f'View {name} 声明的组件{__type}未被实现'
    ctor = getattr(ipywidgets, __type)
    
    if value is None:
        pass
    elif hasattr(ctor, 'value'):
        args['value'] = value
    elif hasattr(ctor, 'children'):
        args['children'] = value
    
    #实例化
    widget = ctor(**args)
    
    # 给模型列表补充本地模型
    if name == 'model_name':
        widget.options = list(widget.options) + \
            [m for m in collect_local_module_names() if m not in widget.options]
    
    # 添加DOM class名
    if 'class_name' in options:
        widget.add_class(options['class_name'])
    # 设置预设布局
    if 'layout_name' in options:
        setLayout(options['layout_name'], widget)
    
    return widget

DEFAULT_BADWORDS = "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry"
def createPromptsView(value = '', negative_value = ''):

    style_sheets = '''
@media (max-width:576px) {
    {root} .box_prompts .prompt > textarea {
        min-height:8rem;
        margin-left:2rem!important;
    }
    {root} .box_prompts .negative_prompt > textarea {
        margin-left:2rem!important;
    }
    {root} .box_prompts > .box_wrap_quikbtns {
        margin-left: 0 !important;
    }
    {root} .box_prompts > .box_wrap_quikbtns > button {
        padding: 0 !important;
    }
}
'''
    
    prompt = createView('prompt', value = value)
        
    negative_prompt = createView('negative_prompt', value = negative_value) 
    
    # 按钮
    btnGoodQuality = Button(
        description= '',
        tooltip='填充标准质量描述',
        icon='palette',
        layout = Layout(
            #不支持的属性？ position = 'absolute',
            height = '1.8rem',
            width = '1.8rem',
            margin = '-11rem 0 0 0'
        )
    )
    btnBadwards = Button(
        description= '',
        tooltip='填充标准负面描述',
        icon='paper-plane',
        layout = Layout(
            #不支持的属性？ position = 'absolute',
            height = '1.8rem',
            width = '1.8rem',
            margin = '-2rem 0px 0rem -1.8rem'
        )
    )
    def fill_good_quality(b):
        if not prompt.value.startswith('masterpiece,best quality,'):
            prompt.value = 'masterpiece,best quality,' + prompt.value
    def fill_bad_words(b):
        negative_prompt.value = DEFAULT_BADWORDS
        
    btnGoodQuality.on_click(fill_good_quality)
    btnBadwards.on_click(fill_bad_words)
        
    box_wrap_quikbtns = Box([
                            btnGoodQuality,btnBadwards,
                        ], layout = Layout(
                            margin = '0 1rem',
                            height = '0',
                            overflow = 'visible'
                        ));
    box_wrap_quikbtns.add_class('box_wrap_quikbtns')
    
    container = Box([
        HBox([prompt]),
        HBox([negative_prompt]),
        box_wrap_quikbtns,
    ])
    container.layout.display = 'block';
    container.add_class('box_prompts')
    
    return Bunch({
        'container': container,
        'prompt': prompt,
        'negative_prompt': negative_prompt,
        'style_sheets': style_sheets,
    })
    
def _create_WHView(width_value = 512, height_value = 512):
    style_sheets = '''
@media (max-width:576px) {
    {root} .box_width_height {
        flex: 8 8 60% !important;
    }
}
'''
    _layout = Layout(
        flex = '1 0 2rem',
        width = '2rem',
    )
    w_width = BoundedIntText(
        layout=_layout,
        value=width_value,
        min=64,
        max=1088,
        step=64,
    )
    w_height = BoundedIntText(
        layout=_layout,
        value=height_value,
        min=64,
        max=1088,
        step=64,
    )
    
    def validate(change):
        num = change.new % 64
        if change.new < 64:
            change.owner.value = 64
        elif num == 0:
            pass
        elif num < 32:
            change.owner.value = change.new - num
        else:
            change.owner.value = change.new - num + 64
    w_width.observe(validate,names = 'value')
    w_height.observe(validate,names = 'value')
        
    container = HBox([
        w_width,
        Label(
            value = 'X',
            layout = Layout(
                flex='0 0 auto',
                padding='0 0.75rem'
            ),
           # description_tooltip = '图片尺寸' if not step64 else '图片尺寸（-1为自动判断）'
        ),
        w_height,
    ])
    setLayout('col04', container)
    container.add_class('box_width_height')
    
    return Bunch({
        'container': container,
        'width': w_width,
        'height': w_height,
        'style_sheets': style_sheets,
    })
    
def _create_WHView_for_img2img(width_value = -1, height_value = -1):
    style_sheets = '''
{root} .box_width_height > .widget-label:first-of-type {
    text-align: right !important;
}
@media (max-width:576px) {
    {root} .box_width_height {
        flex: 8 8 60% !important;
        
        flex-wrap: wrap !important;
        height: auto;
        margin-top: 0.1rem !important;
        margin-bottom: 0.1rem !important;
    }
    {root} .box_width_height > .widget-label:first-of-type {
        width: 100% !important;
        text-align: left !important;
        font-size: small !important;
    }
}
'''
    _layout = Layout(
        flex = '1 0 2rem',
        width = '2rem',
    )
    w_width = IntText(
        layout=_layout,
        value=width_value,
    )
    w_height = IntText(
        layout=_layout,
        value=height_value,
    )
    
    container = HBox([
        Label( 
            value = '图片尺寸',
            description_tooltip = '-1表示自动检测',
            style = _description_style,
            layout = Layout(
                flex='0 0 auto',
                width = '4rem',
                # margin = '0 4px 0 0',
                margin = '0 calc(var(--jp-widgets-inline-margin)*2) 0 0',
            )
        ),
        w_width,
        Label(
            value = 'X',
            layout = Layout(
                flex='0 0 auto',
                padding='0 0.75rem'
            ),
        ),
        w_height,
    ])
    setLayout('col08', container)
    container.add_class('box_width_height')
    
    return Bunch({
        'container': container,
        'width': w_width,
        'height': w_height,
        'style_sheets': style_sheets,
    })

def createWidthHeightView(width_value = 512, height_value = 512, step64 = False):
    if step64:
        return _create_WHView(width_value, height_value)
    else:
        return _create_WHView_for_img2img(width_value, height_value)
        

# --------------------------------------------------
    
def Tab(children = None, **kwargs):
    titles = None if 'titles' not in kwargs else kwargs.pop('titles')
    if children is not None: kwargs['children'] = children
    tab = ipywidgets.Tab(**kwargs)
    if titles is not None:
        for i in range(len(titles)):
            tab.set_title(i, titles[i])
    return tab

def Div(children = None, **kwargs):
    if children is not None: kwargs['children'] = children
    box = Box(**kwargs)
    box.layout.display = 'block' # Box 默认flex
    return box

def FlexBox(children = None, **kwargs):
    if children is not None: kwargs['children'] = children
    box = Box(**kwargs)
    box.layout.display = 'flex'
    box.layout.flex_flow = 'row wrap' # HBox覆写nowrap，Box默认nowrap
    box.layout.justify_content = 'space-around'  #注意覆写
    box.layout.align_items = 'center',
    box.layout.align_content = 'center',
    return box
    