#!/usr/bin/python3
import os
import json
import time

import __main__  as MAIN

from collections import OrderedDict
from lib.cprint import *

HOME = os.getenv('HOME')

def _fixture_decode_sav_line(line):
    out = None
    out = [0,"none",{}]

    if line.count("\t") < 2:
        cprint("Error line.count('\\t') < 2  (is:{})".format(line.count("\t")),color="red",end=" ")
        cprint("file:{}".format(line),color="red")
    else:
        key,label,rdata = line.split("\t",2)
        jdata = json.loads(rdata,object_pairs_hook=OrderedDict)
        key = int(key)
        #label += " dsav"
        #label = label.replace(" dsav","")
        out = [key,label,jdata]

    #if not out:
    #print(line)
    #sys.exit()
    return out

def _fixture_repair_nr0(jdata):
    nrnull = 0
    if "ATTRIBUT" in jdata:  # translate old Fixtures.fixtures start with 0 to 1          
        if nrnull:
            cprint("DMX NR IS NULL",attr,"CHANGE +1")
            for attr in jdata["ATTRIBUT"]:
                if "NR" in jdata["ATTRIBUT"][attr]:
                    nr = jdata["ATTRIBUT"][attr]["NR"]
                    if nr >= 0:
                        jdata["ATTRIBUT"][attr]["NR"] +=1
    #return jdata

def FIXTURE_CHECK_SDATA(ID,sdata):
    #print("FIXTURE_CHECK_SDATA",ID)
    new_f = OrderedDict()
    #print("++++")
    for k,j in sdata.items():
        overide=0 # only for repair
        if overide:
            if k in ["TYPE","VENDOR"]: #ignor
                continue
        new_f[k] = j
        if k =="NAME":
            #print("AAAADDDDDD")
            if "TYPE" not in sdata and not overide:
                if len( sdata["ATTRIBUT"]) == 1:
                    new_f["TYPE"] = "DIMMER"
                elif "PAN" in sdata["ATTRIBUT"]:
                    new_f["TYPE"] = "MOVER"
                elif "RED" in sdata["ATTRIBUT"] and len(sdata["ATTRIBUT"]) == 3:
                    new_f["TYPE"] = "RGB"
                elif "RED" in sdata["ATTRIBUT"]:
                    new_f["TYPE"] = "LED"
                elif "CYAN" in sdata["ATTRIBUT"]:
                    new_f["TYPE"] = "COLOR"
                else:
                    new_f["TYPE"] = ""
            if "VENDOR" not in sdata and not overide:
                new_f["VENDOR"] = ""

        #print(k,j)#,sdata)
    sdata = new_f
    if "ACTIVE" not in sdata:
        sdata["ACTIVE"] = 0

    sdata["ATTRIBUT"]["_ACTIVE"] = OrderedDict()
    sdata["ATTRIBUT"]["_ACTIVE"]["NR"] = 0
    sdata["ATTRIBUT"]["_ACTIVE"]["ACTIVE"] = 1
    sdata["ATTRIBUT"]["_ACTIVE"]["VALUE"] = 0
    sdata["ATTRIBUT"]["_ACTIVE"]["FX2"] = {}
    sdata["ATTRIBUT"]["_ACTIVE"]["FX"] = ""

    DEL = []
    for ATTR in list(sdata["ATTRIBUT"].keys()):
        if ATTR.startswith("_"):
            continue

        if ATTR.endswith(" FINE"):
            ATTR2 = ATTR.replace(" FINE","-FINE")
            ATTR2 = ATTR2.replace(" -","-")
            ATTR2 = ATTR2.replace("  "," ")
            ATTR2 = ATTR2.replace("  "," ")
            ATTR2 = ATTR2.replace("  "," ")
            if ATTR2 not in sdata["ATTRIBUT"]:
                print("  CHECK_SDATA REPAIR",[ATTR,ATTR2])
                sdata["ATTRIBUT"][ATTR2]  = sdata["ATTRIBUT"][ATTR] 
                DEL.append(ATTR)
    for d in DEL:
        if d in sdata["ATTRIBUT"]:
            print("  CHECK_SDATA DEL   ",[d],"!")
            del sdata["ATTRIBUT"][d]

    if "DIM" not in sdata["ATTRIBUT"]:
        _tmp = None
        #print(sdata)
        vdim_count = 0
        for a in ["RED","GREEN","BLUE"]:#,"WHITE","AMBER"]:
            if a in sdata["ATTRIBUT"]:
                vdim_count +=1

        if vdim_count == 3:
            _tmp =  {"NR": 0, "MASTER": "0", "MODE": "F", "VALUE": 255, "ACTIVE": 0, "FX": "", "FX2": {}}
            _tmp = OrderedDict(_tmp)
            sdata["ATTRIBUT"]["DIM"] =_tmp 
        print("   ADD ---- VDIM",vdim_count,_tmp)
        #input("STOP")

    for attr in sdata["ATTRIBUT"]:
        row = sdata["ATTRIBUT"][attr]
        row["ACTIVE"] = 0

        if "FX" not in row:
            row["FX"] =""
        if "FX2" not in row:
            row["FX2"] = {}
        if "MASTER" not in row:
            row["MASTER"] = 0


    if "ID" not in sdata:
        sdata["ID"] = str(ID)
    return sdata


def _parse_fixture_name(name):
    out = []
    #{"FIX","MAN","CH","PATH":""}
    if name.count(".") == 2:
        m,n,e = name.split(".")
        #out = [n,m,"0",name]
        out = {"name":n,"manufactor":m,"fname":name}
    elif name.count("_") == 2:
        name,e = name.split(".")
        m,n,c = name.split("_")
        out = {"name":n,"ch":c,"manufactor":m,"name":name}
        #out = [n,m,c,name]
    else:
        out = {"name":name}
    return out




def index_fixtures():
    p="/opt/LibreLight/Xdesk/fixtures/"
    ls = os.listdir(p )
    ls.sort()
    blist = []
    
    for l in ls:
        b = _parse_fixture_name(l)
        b.append(p)
        b.insert(0,"base")
        blist.append(b)
    return blist



#def _fixture_create_import_list(path=None):
def _fixture_load_import_list(path=None):
    if not path:
        path = "/home/user/LibreLight/show"

    blist = []
    lsd = os.listdir(path)
    lsd.sort()
    fname_buffer = []
    for sname in lsd:
        #print("   ",sname)
        ok = 0
        try:
            fname = path+"/"+sname+"/patch.sav"
            if os.path.isfile(fname):
                ok = 1
            else:
                fname = path+"/"+sname
                if os.path.isfile(fname):
                    ok = 1
            #fname_buffer = []
            if not ok:
                continue

            f = open(fname)
            lines = f.readlines()
            f.close()

            for line in lines:
                ok2 = 0
                _key = ""
                line = line.split("\t")
                if len(line) < 2:
                    continue
                jdata = json.loads(line[2])

                fixture = jdata
                _len = str(fixture_get_ch_count(fixture))
                if "ATTRIBUT" in jdata:
                    #_len = len(jdata["ATTRIBUT"])
                    #if "_ACTIVE" in jdata["ATTRIBUT"]:
                    #    _len -= 1
                    _key = list(jdata["ATTRIBUT"].keys()) 
                    _key.sort()
                    _key = str(_key)
                    if _key not in fname_buffer:
                        fname_buffer.append(_key) # group same fixtures by ATTR
                        ok2 = 1
                if ok2:
                    name = jdata["NAME"]
                    #row = [name,fname+":"+name,path])
                    xfname = fname.replace(path,"")
                    row = {"xfname":xfname ,"name":name,"ch":_len, "xpath":path,"d":_key} #,"b":jdata}
                    blist.append(row)
        except Exception as e:
            print("exception",e)
            raise e
    return blist


def fixture_get_ch_count(fixture):
    _len = [0,0]
    if "ATTRIBUT" not in fixture:
        return [-1,-1]

    for at in fixture["ATTRIBUT"]:
        #print(at,_len)
        #print(" ",fixture["ATTRIBUT"][at])
        if not at.startswith("_") and not at.startswith("EMPTY"):
            _len[1] += 1

        if "NR" in fixture["ATTRIBUT"][at]:
            NR = fixture["ATTRIBUT"][at]["NR"]
            if NR > _len[0]:
                _len[0] = NR
        #print("-",at,_len)

    return _len

def fixture_get_attr_data(fixture,attr):
    if "ATTRIBUT" in fixture:
        if attr in fixture["ATTRIBUT"]:
            return fixture["ATTRIBUT"][attr]

    if "NAME" in fixture:
        print("  NO fixture_get_attr_data A",fixture["NAME"],attr)
    else:
        print("  NO fixture_get_attr_data B",fixture,attr)

def fixture_order_attr_by_nr(fixture):
    out1 = []
    max_nr = 0
    if "ATTRIBUT" not in fixture:
        return []

    nrs = {}
    for at in fixture["ATTRIBUT"]:
        #print("+   ",at)
        atd = fixture_get_attr_data(fixture,at)
        #print("+   ",atd)
        if not atd:
            continue

        k = atd["NR"]
        v = at
        nrs[k] = v
        if k > max_nr:
            max_nr = k

    for i in range(1,max_nr+1):
        if i not in nrs:
            v = "EMPTY" #-{}".format(i)
            nrs[i] = v
            #print("-: ",v)


    nrs_key = list(nrs.keys())
    nrs_key.sort()
    #print(nrs_key)

    for k in nrs_key:
        v = nrs[k]
        #print("-: ",k,v)
        out1.append(v)

    #print()
    return out1 

def _load_fixture_list(mode="None"):
    blist = []

    if mode == "USER":
        path = HOME+"/LibreLight/fixtures/"

    elif mode == "GLOBAL":
        path="/opt/LibreLight/Xdesk/fixtures/"

    elif mode == "IMPORT":
        path=None 

    _r =  _fixture_load_import_list(path=path)
    blist.extend( _r )
    return blist

def get_attr(fixtures,fix,attr):
    if fix in fixtures:
        data = fixtures[fix]
        if "ATTRIBUT" in data:
            if attr in data["ATTRIBUT"]:
                return data["ATTRIBUT"][attr]


def get_dmx(fixtures,fix,attr):
    #cprint("get_dmx",[fix,attr], fix in self.fixtures)
    DMX = -99
    if attr.startswith("_"):
        return -88

    if fix in fixtures:
        data = fixtures[fix]
        if "DMX" in data:
            DMX = int(data["DMX"])
        
        if DMX <= 0:
            return DMX # VIRTUAL FIX

        if "UNIVERS" in data:
            DMX += int(data["UNIVERS"])*512

        #adata = self.get_attr(fix,attr)
        adata = get_attr(fixtures,fix,attr)

        if adata:
            if "NR" in adata:
                NR = adata["NR"] 
                if NR <= 0:
                    return -12 # not a VIRTUAL ATTR
                else:
                    DMX+=NR-1
                return DMX
    return -199

def get_active(fixtures,_filter=""): #_filter only-fx
    cprint("fixlib.get_active",_filter)
    CFG = OrderedDict()

    sdata = OrderedDict()
    sdata["CFG"] = CFG # OrderedDict()
    sdata["CFG"]["FADE"] = MAIN.meta.FADE.val()
    sdata["CFG"]["DEALY"] = 0

    for fix in fixtures:                            
        data = fixtures[fix]

        for attr in data["ATTRIBUT"]:
            if not data["ATTRIBUT"][attr]["ACTIVE"]:
                continue

            if fix not in sdata:
                sdata[fix] = {}

            if attr not in sdata[fix]:
                sdata[fix][attr] = OrderedDict()

                if "ONLY-FX" in _filter:
                    #cprint( "          ONLY FX !!!     -------------------- ")
                    sdata[fix][attr]["VALUE"] = None 
                else:
                    sdata[fix][attr]["VALUE"] = data["ATTRIBUT"][attr]["VALUE"]

                if "FX" not in data["ATTRIBUT"][attr]: 
                     data["ATTRIBUT"][attr]["FX"] = ""

                if "FX2" not in data["ATTRIBUT"][attr]: 
                     data["ATTRIBUT"][attr]["FX2"] = {}
                
                sdata[fix][attr]["FX"] = data["ATTRIBUT"][attr]["FX"] 
                sdata[fix][attr]["FX2"] = data["ATTRIBUT"][attr]["FX2"] 

    return sdata

def _deselect_all(fixtures,fix=None):
    cprint("fixlib._deselect_all()",fix,"ALL",color="yellow")
    c=0
    if fix in fixtures:
        data = fixtures[fix]

        for attr in data["ATTRIBUT"]:
            #print("SELECT ALL",fix,attr)
            if "-FINE" in attr.upper():
                pass
            else:
                c+=select(fixtures,fix,attr,mode="off",mute=1)
    
    return c

def _select_all(fixtures,fix=None,mode="toggle",mute=0):
    if not mute:
        cprint("fixlib._select_all()",fix,"ALL",mode,color="yellow")
    c=0
    if fix in fixtures:
        data = fixtures[fix]
        for attr in data["ATTRIBUT"]:
            #print("SELECT ALL",fix,attr)
            if "-FINE" in attr.upper():
                continue
            
            if mode == "toggle":
                c+=select(fixtures,fix,attr,mode="on",mute=mute)
            elif mode == "swap":
                if not attr.startswith("_"):
                    c+=select(fixtures,fix,attr,mode="toggle",mute=mute)

        if not c and mode == "toggle": # unselect all
            c= _deselect_all(fixtures,fix=fix)
    return c 

def select(fixtures,fix=None,attr=None,mode="on",mute=0):
    if not mute:
        cprint("fixlib.select() >>",fix,attr,mode,color="yellow")
    out = 0

    if fix == "SEL":
        if attr.upper() == "INV-ATTR":
            fixs = get_active(fixtures)
            cprint("selected:",len(fixs))
            for fix in fixs:
                x=_select_all(fixtures,fix=fix,mode=mode,mute=1)
            return None 

    if fix in fixtures:
        if attr.upper() == "ALL":
            x=_select_all(fixtures,fix=fix,mode=mode)
            return x

        data = fixtures[fix]
        if attr in data["ATTRIBUT"]:
            if mode == "on":
                if not data["ATTRIBUT"][attr]["ACTIVE"]:
                    data["ATTRIBUT"][attr]["ACTIVE"] = 1
                    data["ATTRIBUT"]["_ACTIVE"]["ACTIVE"] = 1
                    out = 1
            elif mode == "off":
                if data["ATTRIBUT"][attr]["ACTIVE"]:
                    data["ATTRIBUT"][attr]["ACTIVE"] = 0
                    out = 1
            elif mode == "toggle":
                if data["ATTRIBUT"][attr]["ACTIVE"]:
                    data["ATTRIBUT"][attr]["ACTIVE"] = 0
                else:
                    data["ATTRIBUT"][attr]["ACTIVE"] = 1
                    data["ATTRIBUT"]["_ACTIVE"]["ACTIVE"] = 1
                out = 1
    return out

def clear(fixtures):
    out = 0
    for fix in fixtures:
        data = fixtures[fix]
        for attr in data["ATTRIBUT"]:
            #if attr.endswith("-FINE"):
            #    continue
            if data["ATTRIBUT"][attr]["ACTIVE"]:
                out +=1
            data["ATTRIBUT"][attr]["ACTIVE"] = 0
    return out



def encoder(fixtures,fix,attr,xval="",xfade=0,xdelay=0,blind=0):

    _blind = 0
    if MAIN.modes.val("BLIND"):
        _blind = 1 
    if blind:
        _blind = 1
    
    if not _blind:
        cprint("fixlib.encoder",fix,attr,xval,xfade,color="yellow")

    if attr == "CLEAR":
        clear(fixtures)
        return 0

    if attr == "ALL":
        x=select(fixtures,fix,attr,mode="toggle")
        return x

    if attr == "INV-ATTR":
        cprint("-x-x-x-x-x-x-x-X-")
        x=select(fixtures,fix,attr,mode="swap")
        MAIN.master.refresh_fix()
        return x
    if attr == "INV-FIX":
        cprint("-x-x-x-x-x-x-x-x-")
        x=select(fixtures,fix,attr,mode="swap")
        return x
    out = []

    if fix not in fixtures: 
        ii =0
        delay=0
        sstart = time.time()
        sub_data = []
        for _fix in fixtures:
            ii+=1
            data = fixtures[_fix]
            if "-FINE" in attr.upper():
                continue

            elif (attr in data["ATTRIBUT"] ) and "-FINE" not in attr.upper()   :
                if xval == "click":
                    select(fixtures,_fix,attr,mode="on")
                elif data["ATTRIBUT"][attr]["ACTIVE"]:
                    if _fix:
                        sub_data.append([_fix,attr,xval,xfade,delay])
            if MAIN.meta.DELAY._is():
                pass #delay += MAIN.meta.DELAY.val()/100

        sub_jdata = []
        for dd in sub_data:
            _x123 = encoder(fixtures,dd[0],dd[1],dd[2],dd[3],dd[4],1)
            sub_jdata.append(_x123)

        if sub_jdata:
            cprint("  SEND MASTER ENCODER:",len(sub_data),sub_data[0],"... _blind:",_blind)#,end="")
            if not _blind:
                MAIN.jclient_send(sub_jdata) 
        #for ia in sub_jdata:
        #    print("SOND:",ia)
        jdata=[{"MODE":ii}]

        if not _blind:
           MAIN.jclient_send(jdata)
        return sub_jdata  #len(sub_data)

    data = fixtures[fix]

    if xval == "click":
        return select(fixtures,fix,attr,mode="toggle")


    v2=data["ATTRIBUT"][attr]["VALUE"]
    change=0
    increment = 5 #4.11
    jdata = {"MODE":"ENC"}
    if xval == "++":
        v2+= increment
        jdata["INC"] = increment
        change=1
    elif xval == "--":
        jdata["INC"] = increment*-1
        v2-= increment
        change=1
    elif xval == "+":
        increment = 0.25 #.5
        v2+= increment
        jdata["INC"] = increment
        change=1
    elif xval == "-":
        increment = 0.25 #.5
        jdata["INC"] = increment*-1
        v2-= increment
        change=1
    elif type(xval) is int or type(xval) is float:
        v2 = xval 
        change=1

        
    if v2 < 0:
        v2=0
    elif v2 > 256:
        v2=256

    jdata["VALUE"]    = round(v2,4)
    jdata["FIX"]      = fix
    jdata["FADE"]     = 0
    jdata["DELAY"]    = 0
    jdata["ATTR"]     = attr
    dmx               = get_dmx(fixtures,fix,attr)
    jdata["DMX"]      = dmx

    dmx_fine = get_dmx(fixtures,fix,attr+"-FINE")
    if dmx_fine != jdata["DMX"] and dmx > 0:
        jdata["DMX-FINE"] = dmx_fine

    out = {} 
    if 1: #change:
        data["ATTRIBUT"][attr]["ACTIVE"] = 1
        data["ATTRIBUT"]["_ACTIVE"]["ACTIVE"] = 1
        data["ATTRIBUT"][attr]["VALUE"] = round(v2,4)

        if xfade:
            jdata["FADE"] = xfade

        if xdelay:
            #if attr not in ["PAN","TILT"] and 1:
            jdata["DELAY"] = xdelay

        if not _blind:
            jdata = [jdata]
            MAIN.jclient_send(jdata)
            time.sleep(0.001)

    return jdata




import lib.showlib as showlib

class Fixtures():
    def __init__(self):
        #super().__init__() 
        self.base=showlib.Base()
        #self.load()
        self.fixtures = OrderedDict()

    def load_patch(self):
        cprint("Fixtures.load_patch ..")
        filename="patch"
        d,l = self.base._load(filename)
        self.fixtures = OrderedDict()
        for i in l:
            sdata = d[i]
            #sdata = self._repair_sdata(sdata)
            sdata = FIXTURE_CHECK_SDATA(i,sdata)

            self.fixtures[str(i)] = sdata
        self._re_sort()
        self.fx_off("all")

    def _re_sort(self):
        keys = list(self.fixtures.keys())
        keys2=[]
        for k in keys:
            #k = "{:0>5}".format(k)
            k = int(k)
            keys2.append(k)
        keys2.sort()
        fixtures2 = OrderedDict()
        for k in keys2:
            k = str(k)
            fixtures2[k] = self.fixtures[k]


        self.fixtures = fixtures2

    def backup_patch(self,save_as="",new=0):
        filename = "patch"
        #self.fx_off("all")
        data  = self.fixtures
        labels = {}
        for k in data:
            labels[k] = k
        if new:
            data = []
            labels = {}
        return self.base._backup(filename,data,labels,save_as)

    def fx_get(self,fix=None):
        out={}
        if not fix or fix == "all":
            for fix in self.fixtures:
                data = self.fixtures[fix]
                for attr in data["ATTRIBUT"]:
                    out[str(fix)+"."+str(attr)+".fx"] =  data["ATTRIBUT"][attr]["FX"] 
                    out[str(fix)+"."+str(attr)+".fx"] =  data["ATTRIBUT"][attr]["FX2"]

        return out
    def fx_off(self,fix=None):
        if not fix or fix == "all":
            for fix in self.fixtures:
                data = self.fixtures[fix]
                for attr in data["ATTRIBUT"]:
                    data["ATTRIBUT"][attr]["FX"] = ""
                    data["ATTRIBUT"][attr]["FX2"] = OrderedDict()

    def get_max_dmx_nr(self,fix):
        max_dmx = 0
        used_dmx = 0
        if fix not in self.fixtures:
            return (used_dmx,max_dmx)

        data = self.fixtures[fix]
        used_dmx = len(data["ATTRIBUT"])
        for a in data["ATTRIBUT"]:
            attr = data["ATTRIBUT"][a]
            if "NR" in attr:
                try:
                    _n = int(attr["NR"])
                    if _n > max_dmx:
                        max_dmx=_n
                except ValueError:pass
        return (used_dmx,max_dmx)


    def update_raw(self,rdata,update=1):
        #cprint("update_raw",len(rdata))
        cmd = []
        for i,d in enumerate(rdata):
            xcmd = {"DMX":""}
            fix   = d["FIX"]
            attr  = d["ATTR"]
            v2    = d["VALUE"]
            v2_fx = d["FX"]

            if fix not in self.fixtures:
                continue 

            sdata = self.fixtures[fix] #shortcat

            ATTR  = sdata["ATTRIBUT"] 
            if attr not in ATTR:
                continue

            sDMX = get_dmx(self.fixtures,fix,attr)
            #print(sDMX)
            xcmd["DMX"] = str(sDMX)

            cmd.append(xcmd)

            v=ATTR[attr]["VALUE"]
            if v2 is not None and update:
                ATTR[attr]["VALUE"] = v2
            
            if d["FX2"] and update:
                ATTR[attr]["FX2"] = d["FX2"] 

            text = str(attr)+' '+str(round(v,2))
        return cmd