package com.xixun.xixunplayer;

import static android.view.View.VISIBLE;

import android.app.ActivityManager;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.os.StatFs;
import android.webkit.WebView;

import androidx.annotation.OptIn;
import androidx.media3.common.util.UnstableApi;

import net.lingala.zip4j.ZipFile;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.TimeZone;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;

import gnph.util.IOs;
import gnph.util.JSList;
import gnph.util.JSMap;
import gnph.util.NumFmts;
import gnph.util.O;

public class SocketThread extends Thread {
    MainActivity main;
    Socket socket;
    InputStream in;
    OutputStream out;
    SimpleDateFormat fmt = new SimpleDateFormat("MM-dd HH:mm:ss");

    public SocketThread(MainActivity main, Socket socket) {
        this.main = main;
        this.socket = socket;
    }
    @OptIn(markerClass = UnstableApi.class) @Override
    public void run() {
        try {
            socket.setSoTimeout(600000);
            Util.socketThreads.add(this);
            in = socket.getInputStream();
            out = socket.getOutputStream();
            HashSet<String> existed = null;
            ByteArrayOutputStream progJson = null;
            while(true) {
                var obj = JSMap.from(in);
                var _type = obj.stnn("_type");
                Util.println("_type: "+_type);
                if("consult".equals(_type)) {
                    JSList<JSMap> files = obj.jslist("files");
                    if(files==null) new JSMap("_type", _type, "idList", obj.jslist("idList")).write(out);
                    else {
                        existed = new HashSet<>();
                        for(var fil : files) {
                            var name = fil.stnn("name");
                            var file = new File(Util.programDir + "/" + name);
                            if(file.isFile() && file.length()==fil.intg("size")) existed.add(name);
                        }
                        new JSMap("_type", _type, "existed", existed).write(out);
                    }
                } else if("proStart".equals(_type)) {
                    Util.deleteFiles(obj.intg("proSize"), existed);
                } else if("fileStart".equals(_type)) {
                    var size = obj.intg("size");
                    var name = obj.stnn("id");
                    Util.println("  size: " + size + "  name: " + name);
                    if(name.equals("program")) {
                        progJson = new ByteArrayOutputStream();
                        IOs.writeCloseOut(progJson, in, size);
                    } else {
                        var fOut = new FileOutputStream(Util.programDir + "/" + name);
                        try {
                            IOs.write(fOut, in, size);
                            fOut.flush();
                            fOut.getFD().sync();
                            fOut.close();
                        } catch(Throwable e) {
                            fOut.close();
                            new File(Util.programDir + "/" + name).delete();
                        }
                    }
                } else if("SetBackImg".equals(_type) || "imgFileStart".equals(_type)) {
                    var size = obj.intg("size");
                    var fout = new FileOutputStream(Util.backImgFile);
                    IOs.write(fout, in, size);
                    fout.flush();
                    fout.getFD().sync();
                    fout.close();
                    main.runOnUiThread(() -> {
                        main.backView.cosImg = BitmapFactory.decodeFile(Util.backImgFile, Util.noScaled);
                        main.backView.invalidate();
                    });
                    new JSMap("success", true).write(out);
                } else if("proEnd".equals(_type)) {
                    new JSMap("success", progJson!=null).write(out);
                    if(progJson!=null) {
                        var json = progJson.toByteArray();
                        progJson = null;
                        main.runOnUiThread(() -> main.initProg(json));
                    }
                } else if("playZipTask".equals(_type)) {
                    var zip = new ZipFile(Util.programDir+"/"+obj.stnn("proName"));
                    if(zip.isEncrypted()) zip.setPassword(new char[]{'8','8','8'});
                    long size = 0;
                    ByteArrayOutputStream jsonOut = null;
                    try {
                        var headers = zip.getFileHeaders();
                        for(var header : headers) {
                            size += header.getUncompressedSize();
                            if("program".equals(header.getFileName())) IOs.writeClose(jsonOut = new ByteArrayOutputStream(), zip.getInputStream(header));
                        }
                        if(jsonOut==null) {
                            Util.println("No 'program' File");
                            new JSMap("success", false, "msg", "No 'program' File").write(out);
                            main.runOnUiThread(() -> Util.makeText(main, "No program File").show());
                        } else if(size==0) {
                            Util.println("Zip Size is 0");
                            new JSMap("success", false, "msg", "Zip Size is 0").write(out);
                            main.runOnUiThread(() -> Util.makeText(main, "zip size is 0").show());
                        } else {
                            Util.deleteFiles(size, null);
                            for(var header : headers) if(! "program".equals(header.getFileName())) {
                                Util.println("  name: " + header.getFileName());
                                var fOut = new FileOutputStream(Util.programDir + "/" + header.getFileName());
                                IOs.writeCloseIn(fOut, zip.getInputStream(header));
                                fOut.flush();
                                fOut.getFD().sync();
                                fOut.close();
                            }
                            var json = jsonOut.toByteArray();
                            main.runOnUiThread(()->main.initProg(json));
                            Util.println("  Succeed");
                            new JSMap("success", true).write(out);
                        }
                    } catch (Exception e) {
                        Util.printStackTrace(e);
                        new JSMap("success", false).write(out);
                        main.runOnUiThread(() -> Util.makeText(main, Util.toStr(e)).show());
                    }
                } else if("DelPrograms".equals(_type)) {
                    var latch = new CountDownLatch(1);
                    var ok = new AtomicBoolean(false);
                    main.runOnUiThread(() -> {
                        ok.set(main.delProgFile());
                        latch.countDown();
                    });
                    try {
                        latch.await();
                    } catch (InterruptedException ignored) {}
                    new JSMap("success", ok.get()).write(out);
                } else if("DelBackImg".equals(_type)) {
                    MainActivity.ins.runOnUiThread(() -> {
                        MainActivity.ins.backView.cosImg = null;
                        MainActivity.ins.backView.invalidate();
                    });
                    new JSMap("success", new File(Util.backImgFile).delete()).write(out);
                } else if("getPlayerState".equals(_type)) {
                    var descs = Util.getState(main.state);
                    new JSMap(
                            "code", main.state,
                            "des_en", descs[0],
                            "des", descs[1]
                    ).write(out);
                } else if("GetInfo".equals(_type)) {
                    var writer = new OutputStreamWriter(out);
                    var fmt = new SimpleDateFormat("yy-MM-dd HH:mm:ss.SSS");
                    var dur = 0;
                    if(main.progView!=null) for(var page : main.progView.pages) dur += page.tDur;
                    writer.append("ProgSend: ").append(fmt.format(new File(Util.programDir + "/program").lastModified())).append(" ProgDur: ").append(String.valueOf(dur)).append("ms");
                    if(main.progView!=null) writer.append(" Size Avas: ").append(String.valueOf(main.progView.avas.size())).append(" Pages: ").append(String.valueOf(main.progView.pages.size()));
                    writer.append("\n");
                    writer.append("  Launch: ").append(fmt.format(main.launchMilli)).append("\n");
                    writer.append("    Sync: ").append(fmt.format(main.syncMs)).append("\n");
                    if(main.progView==null) writer.append("  ProgView is Null\n");
                    else if(main.progView.avas.isEmpty()) writer.append("  No Avas\n");
                    else writer.append("Page End: ").append(fmt.format(main.progView.curAva().endMilli)).append(" CurPage: ").append(String.valueOf(main.progView.curAva)).append(" / ").append(String.valueOf(main.progView.avas.get(main.progView.curAva))).append("\n");
                    writer.append(" Current: ").append(fmt.format(System.currentTimeMillis())).append(" ").append(String.valueOf(TimeZone.getDefault().getRawOffset()/3600000f)).append(" ").append(TimeZone.getDefault().getDisplayName()).append("\n");
                    var statFs = new StatFs(Environment.getExternalStorageDirectory().getPath());
                    writer.append("   Disk: ").append(String.valueOf(statFs.getTotalBytes()/1000000)).append(" ").append(String.valueOf(statFs.getAvailableBytes()/1000000)).append(" ").append(String.valueOf(statFs.getFreeBytes()/1000000)).append(" (total avail free)\n");
                    var actManager = (ActivityManager) main.getSystemService(main.ACTIVITY_SERVICE);
                    var memoryInfo = new ActivityManager.MemoryInfo();
                    actManager.getMemoryInfo(memoryInfo);
                    writer.append(" Memory: ").append(String.valueOf(memoryInfo.totalMem/1000000)).append(" ").append(String.valueOf(memoryInfo.availMem/1000000)).append(" ").append(String.valueOf(memoryInfo.threshold/1000000)).append(" ").append(String.valueOf(memoryInfo.lowMemory)).append(" (total avail threshold low)\n");
                    var runtime = Runtime.getRuntime();
                    writer.append("Runtime: ").append(String.valueOf(runtime.maxMemory()/1000000)).append(" ").append(String.valueOf(runtime.totalMemory()/1000000)).append(" ").append(NumFmts.cfix2().format(runtime.freeMemory()*0.000001)).append(" (max total free)\n");
//                    writer.append("/proc/stat\n");
//                    IOs.writeCloseIn(writer, new FileReader("/proc/stat"));
                    writer.append("\nSockets ").append(String.valueOf(Util.socketThreads.size())).append("\n");
                    for(var socket : Util.socketThreads) writer.append("  ").append(String.valueOf(socket.socket.getInetAddress())).append(":").append(String.valueOf(socket.socket.getPort())).append("  Buf:").append(String.valueOf(socket.socket.getReceiveBufferSize()/1000)).append("k SoLinger:").append(String.valueOf(socket.socket.getSoLinger())).append("\n");

                    writer.append("\n");
                    var latch = new CountDownLatch(1);
                    main.runOnUiThread(() -> {
                        if(main.progView!=null && ! main.progView.avas.isEmpty()) {
                            var page = main.progView.curAva();
                            for(var layer : page.layers) for(var src : layer.srcs) if(src.view.getVisibility()==VISIBLE) {
                                try {
                                    if(src.view instanceof SrcVideo) {
                                        var view = (SrcVideo) src.view;
                                        if(view.ijkPlayer!=null) {
                                            writer.append("VideoPlaying: ").append(String.valueOf(view.ijkPlayer.isPlaying())).append("\tCur/Dur: ").append(String.valueOf(view.ijkPlayer.getCurrentPosition())).append("/").append(String.valueOf(view.ijkPlayer.getDuration())).append("\n");
                                            var mediaInfo = view.ijkPlayer.getMediaInfo();
                                            var vStream = mediaInfo.mMeta.mVideoStream;
                                            writer.append("     BitRate: ").append(String.valueOf(view.ijkPlayer.getBitRate()/1000)).append("k\tFPS: ").append(String.valueOf(vStream.mFpsNum/(float)vStream.mFpsDen)).append(" (").append(String.valueOf(vStream.mFpsNum)).append("/").append(String.valueOf(vStream.mFpsDen)).append(")\n");
                                            var tracks = view.ijkPlayer.getTrackInfo();
                                            for(var track : tracks) writer.append("  ").append(track.getInfoInline()).append("\n");
                                            writer.append("      Format: ").append(mediaInfo.mMeta.mFormat).append("\n");
                                            writer.append("VideoDecoder: ").append(mediaInfo.mVideoDecoder).append(" ").append(mediaInfo.mVideoDecoderImpl).append(" (").append(String.valueOf(view.ijkPlayer.getVideoDecoder())).append(")\n");
                                            writer.append("AudioDecoder: ").append(mediaInfo.mAudioDecoder).append(" ").append(mediaInfo.mAudioDecoderImpl).append("\n");
                                            //writer.append("PROFILE: ").append(mediaInfo.mMeta.getString(IjkMediaMeta.IJKM_KEY_CODEC_PROFILE)).append("\n");
                                        } else if(view.exoPlayer!=null) {
                                            writer.append("VideoPlaying: ").append(String.valueOf(view.exoPlayer.isPlaying())).append(" ").append(String.valueOf(view.exoPlayer.getCurrentPosition())).append("/").append(String.valueOf(view.exoPlayer.getDuration())).append("\n");
                                            writer.append("     BitRate: ").append(String.valueOf(view.bitRate/1000)).append("k\n");
                                            writer.append("PlaybackStat: ").append(view.getState()).append("\n");
                                            writer.append(" PlayerError: ").append(String.valueOf(view.exoPlayer.getPlayerError())).append("\n");
                                            writer.append(" VideoFormat: ").append(String.valueOf(view.exoPlayer.getVideoFormat())).append("\n");
                                            var cnt = view.exoPlayer.getRendererCount();
                                            for(int rr=0; rr<cnt; rr++) writer.append(" Renderer: ").append(String.valueOf(view.exoPlayer.getRendererType(rr))).append(" ").append(String.valueOf(view.exoPlayer.getRenderer(rr))).append("\n");
                                            writer.append("getPlaybackParameters: ").append(String.valueOf(view.exoPlayer.getPlaybackParameters())).append("\n");
                                        }
                                        writer.append("\n");
                                    } else if(src.view instanceof SrcWeather) {
                                        var view = (SrcWeather) src.view;
                                        writer.append("SrcWeather html: ").append(view.html).append("\n");
                                    } else if(src.view instanceof WebView) {
                                        var view = (WebView) src.view;
                                        writer.append("Title: ").append(view.getTitle()).append("\n");
                                        writer.append("  Url: ").append(view.getUrl()).append("\n");
                                        writer.append("OrUrl: ").append(view.getOriginalUrl()).append("\n");
                                        writer.append("Progress: ").append(String.valueOf(view.getProgress())).append("\n");
                                    }
                                } catch (Exception ignored) {
                                }
                            }
                        }
                        latch.countDown();
                    });
                    try {
                        latch.await();
                    } catch (InterruptedException ignored) {}
                    writer.flush();
                } else if("GetLog".equals(_type)) {
                    out.write(Util.buf.toString().getBytes());
                } else if("ListProgFiles".equals(_type)) {
                    var files = new File(Util.programDir).listFiles();
                    if(files!=null) {
                        Arrays.sort(files, (f1, f2) -> (int) (f2.lastModified() - f1.lastModified()));
                        var writer = new OutputStreamWriter(out);
                        for(var file : files) writer.append(fmt.format(new Date(file.lastModified()))).append(' ').append(file.getName()).append(' ').append(String.valueOf(file.length())).append('\n');
                        writer.flush();
                    }
                } else if("GetFile".equals(_type)) {
                    var name = obj.str("name");
                    if(name==null) new JSMap("msg", "name is null").write(out);
                    else {
                        var file = new File(Util.programDir+"/"+name);
                        if(! file.isFile()) new JSMap("msg", "file not exist").write(out);
                        else {
                            new JSMap("len", file.length()).write(out);
                            IOs.writeCloseIn(out, new FileInputStream(file));
                        }
                    }
                } else if("GetJsonWithFileInfo".equals(_type)) {
                    var prog = new File(Util.programDir+"/program");
                    if(! prog.isFile()) new JSMap("msg", "'program' file not exist").write(out);
                    else {
                        var json = IOs.readStrClose(new FileInputStream(prog));
                        var files = new File(Util.programDir).listFiles();
                        if(files!=null) for(var file : files) if(! "program".equals(file.getName())) json = json.replace("\""+file.getName()+"\"", "\""+file.getName()+"\"/*"+file.length()+" "+fmt.format(new Date(file.lastModified()))+"*/");
                        var writer = new OutputStreamWriter(out);
                        writer.append(json);
                        writer.flush();
                    }
                }
                out.flush();
                Util.println("cmd end");
            }
        } catch (Throwable e) {
            var emsg = e.getMessage();
            if(e instanceof SocketTimeoutException || "Socket closed".equals(emsg) || (emsg!=null && emsg.endsWith("end-of-input"))) {
                Util.println(emsg);
                return;
            }
            main.runOnUiThread(() -> Util.makeText(main, Util.toStr(e)).show());
            Util.printStackTrace(e);
        } finally {
            Util.socketThreads.remove(this);
            O.close(in, out, socket);
            Util.println("conn end\n");
        }
    }
}
