package com.xixun.xixunplayer;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.os.StrictMode;
import android.view.Choreographer;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.xixun.joey.aidlset.CardService;
import com.xixun.xy.conn.aidl.ConnService;

import net.lingala.zip4j.ZipFile;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.HashSet;

import gnph.util.Chsets;
import gnph.util.IOs;
import gnph.util.JSList;
import gnph.util.JSMap;

public class MainActivity extends Activity implements Choreographer.FrameCallback, Runnable {

    public static MainActivity ins;
    ArrayList<BroadcastReceiver> reces = new ArrayList<>();
    public Intent environIntent = new Intent();
    HashSet<IntentReceiver> environs = new HashSet<>();
    BackView backView;
    Prog progView, insView;
    long launchMilli = System.currentTimeMillis();
    long syncMs;
    int state;

    @RequiresApi(api = Build.VERSION_CODES.R)
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ins = this;
        Util.println("==>> MainActivity onCreate >>>> this "+hashCode()+" UI Thread: "+Thread.currentThread().getId());
        try{
            if(RestartService.ins==null) startService(new Intent(this, RestartService.class));
        } catch(Throwable e) {
            Util.printStackTrace(e);
        }

        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().permitAll().build());

        if(ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) init();
        else {
            Util.println("---- No permission, Try again ...");
            Util.makeText(this, "No permission, Try again ...").show();
            ActivityCompat.requestPermissions(this, new String[]{
                android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
                android.Manifest.permission.READ_EXTERNAL_STORAGE,
                android.Manifest.permission.MANAGE_EXTERNAL_STORAGE,
                android.Manifest.permission.RECEIVE_BOOT_COMPLETED,
                android.Manifest.permission.INTERNET
            }, 999);
        }
    }
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if(backView!=null) return;
        if(requestCode==999 && grantResults.length > 0 && grantResults[0]==PackageManager.PERMISSION_GRANTED) init();
        else {
            Util.println("---- Request Permission Failed");
            Util.makeText(this, "Request Permission Failed").show();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Util.println("==<< MainActivity onDestroy <<<< this "+hashCode());
        state = 8;
        if(insView!=null) {
            insView.release();
            insView = null;
        }
        if(progView!=null) {
            progView.release();
            progView = null;
        }
        ins = null;
        for(var rece : reces) {
            try {
                unregisterReceiver(rece);
            } catch (Throwable ignored){
            }
        }
        System.gc();
    }

    @Override
    protected void onStart() {
        super.onStart();
        Util.println("  ==>> MainActivity onStart >> "+hashCode());
    }
    @Override
    protected void onStop() {
        super.onStop();
        Util.println("  ==<< MainActivity onStop << "+hashCode());
    }
    @Override
    protected void onRestart() {
        super.onRestart();
        Util.println("  ==>> MainActivity onRestart >> "+hashCode());
    }

    @Override
    protected void onResume() {
        super.onResume();
        Util.println("  ==>> MainActivity onResume >> "+hashCode());
    }

    @Override
    protected void onPause() {
        super.onPause();
        Util.println("  ==<< MainActivity onPause << "+hashCode());
    }

    CardService serviCard;
    ConnService serviXy;
    Intent intenCard;
    ServiceConnection connCard;
    @SuppressLint("UnspecifiedRegisterReceiverFlag")
    public void init() {
        var dir = Build.VERSION.SDK_INT < Build.VERSION_CODES.R ? Environment.getExternalStorageDirectory().getAbsolutePath() + "/XixunPlayer" : getExternalFilesDir(null).getAbsolutePath();
        Util.println("\n---- Init ----\n  dir "+dir);
        Util.programDir = dir + "/program";
        Util.backImgFile = dir + "/background";
        setContentView(backView = new BackView(MainActivity.this));
        state = 5;

        var program = new File(Util.programDir);
        if(program.isFile()) program.delete();
        Util.println("  mkdir: "+program.mkdirs());

        connCard = new ServiceConnection() {
            public void onServiceDisconnected(ComponentName name) {
                serviCard = null;
                Util.println("<-<- AIDL Service cardsystem Disconnected");
            }

            public void onServiceConnected(ComponentName name, IBinder iBinder) {
                unbindService(this);
                Util.println("->-> AIDL Service cardsystem Connected");
                serviCard = CardService.Stub.asInterface(iBinder);
                try {
                    Util.isScreenOn = serviCard.isScreenOpen();
                    Util.screenWidth = serviCard.getScreenWidth();
                    Util.screenHeight = serviCard.getScreenHeight();
                    Util.println("  IsScreenOn: "+Util.isScreenOn+" screen: "+Util.screenWidth+" x "+Util.screenHeight);
                    backView.invalidate();
                    if(Util.isScreenOn) initProg();
                    else state = 8;
                } catch (Exception e) {
                    Util.makeText(MainActivity.this, Util.toStr(e)).show();
                    Util.printStackTrace(e);
                }
            }
        };
        intenCard = new Intent("com.xixun.joey.aidlset.SettingsService");
        intenCard.setPackage("com.xixun.joey.cardsystem");
        bindService(intenCard, connCard, Context.BIND_AUTO_CREATE);

        var connXy = new ServiceConnection() {
            public void onServiceDisconnected(ComponentName name) {
                serviXy = null;
                Util.println("<-<- AIDL Service xy.conn Disconnected");
            }
            public void onServiceConnected(ComponentName name, IBinder iBinder) {
                unbindService(this);
                Util.println("->-> AIDL Service xy.conn Connected");
                serviXy = ConnService.Stub.asInterface(iBinder);
                try {
                    Util.serverURL = serviXy.getServerURL();
                    Util.println("  ServerURL: "+Util.serverURL);
                    if(Util.serverURL==null || Util.serverURL.isEmpty()) Util.serverURL = "https://m2mled.net/";
                    else {
                        if(! Util.serverURL.startsWith("http")) Util.serverURL = "http://"+Util.serverURL;
                        if(! Util.serverURL.endsWith("/")) Util.serverURL += "/";
                    }
                } catch (Exception e) {
                    Util.makeText(MainActivity.this, Util.toStr(e)).show();
                    Util.printStackTrace(e);
                }
            }
        };
        var intenXy = new Intent("xixun.intent.action.CONNECTION_INFO");
        intenXy.setPackage("com.xixun.xy.conn");
        bindService(intenXy, connXy, Context.BIND_AUTO_CREATE);

        reces.clear();
        BroadcastReceiver rece;
        registerReceiver(rece = new BroadcastReceiver(){
            @Override
            public void onReceive(Context context, Intent intent) {
                Util.println("Receive PAUSE_PLAYER");
                Util.isScreenOn = ! intent.getBooleanExtra("pause", false);
                Util.println("  IsScreenOn: "+Util.isScreenOn);
                if(! Util.isScreenOn) {
                    state = 8;
                    if(insView!=null) {
                        insView.release();
                        insView = null;
                    }
                    if(progView!=null) {
                        progView.release();
                        progView = null;
                    }
                    setContentView(backView);
                } else if(progView==null && insView==null) MainActivity.ins.runOnUiThread(() -> initProg());
                System.gc();
            }
        }, new IntentFilter("com.xixun.action.PAUSE_PLAYER"));
        reces.add(rece);

        registerReceiver(rece = new BroadcastReceiver(){
            @Override
            public void onReceive(Context context, Intent intent) {
                Util.println("Receive CHANGE_COMPANYID");
                try {
                    if(serviXy!=null && serviXy.asBinder().isBinderAlive()) {
                        Util.serverURL = serviXy.getServerURL();
                        Util.println("  ServerURL: "+Util.serverURL);
                        if(Util.serverURL==null || Util.serverURL.isEmpty()) Util.serverURL = "https://m2mled.net/";
                        else {
                            if(! Util.serverURL.startsWith("http")) Util.serverURL = "http://"+Util.serverURL;
                            if(! Util.serverURL.endsWith("/")) Util.serverURL += "/";
                        }
                    } else {
                        Util.println("  bindService");
                        bindService(intenXy, connXy, Context.BIND_AUTO_CREATE);
                    }
                } catch (Exception e) {
                    Util.makeText(MainActivity.this, Util.toStr(e)).show();
                    Util.printStackTrace(e);
                }
            }
        }, new IntentFilter("com.xixun.joey.CHANGE_COMPANYID"));
        reces.add(rece);

        registerReceiver(rece = new BroadcastReceiver(){
            @Override
            public void onReceive(Context context, Intent intent) {
                MainActivity.this.environIntent = intent;
                for(var environ : environs) {
                    environ.onReceive(intent);
                    ((View)environ).invalidate();
                }
            }
        }, new IntentFilter("xixun.intent.action.TEMPERATURE_HUMIDITY"));
        reces.add(rece);

        registerReceiver(rece = new BroadcastReceiver(){
            @Override
            public void onReceive(Context context, Intent intent) {
                try {
                    var code = intent.getIntExtra("code", 0);
                    Util.println("  remote_control "+code);
                    if(progView == null || ! progView.isShown() || code > progView.pages.size()) return;
                    var ms = System.currentTimeMillis();
                    if(! avas.isEmpty()) {
                        avas.get(curAva).hide();
                        if(code > 0 && avas.get(curAva)==progView.pages.get(code-1)) for(var call : progView.calls) call.doFrame(ms);
                    }
                    ms = (ms+999)/1000*1000;
                    if(code > 0) {
                        avas.clear();
                        var page = progView.pages.get(code-1);
                        avas.add(page);
                        curAva = 0;
                        curTimes = 1;
                        waitTo = 0; //点播
                        page.setMillis(ms, ms);
                        if(state != 6) {
                            setContentView(progView);
                            state = 6;
                        }
                    } else {
                        waitTo = Long.MAX_VALUE;
                        syncProg(ms, 0);
                    }
                } catch (Throwable e) {
                    Util.makeText(MainActivity.this, Util.toStr(e)).show();
                    Util.printStackTrace(e);
                }
            }
        }, new IntentFilter("com.xixun.yzd.REMOTE_CONTROL"));
        reces.add(rece);

        var intentFilter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
        intentFilter.addDataScheme("file");
        registerReceiver(rece = new BroadcastReceiver(){
            long lastMs;
            final char[] pass = {'8','8','8'};
            @Override
            public void onReceive(Context context, Intent intent) {
                var path = intent.getData().getPath();
                Util.println("\nMEDIA_MOUNTED path: "+path);
                var ms = System.currentTimeMillis();
                if(ms-lastMs<1000) return;
                lastMs = ms;
                Util.makeText(MainActivity.this, "MEDIA_MOUNTED path: "+path+"\nImporting 正在导入 ...").show();
                new Thread(()->{
                    try {
                        var zip = new ZipFile(path+"/program.zip");
                        if(zip.isEncrypted()) zip.setPassword(pass);
                        long size = 0;
                        ByteArrayOutputStream jsonOut = null;
                        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");
                            runOnUiThread(() -> Util.makeText(MainActivity.this, "No program File").show());
                            return;
                        }
                        if(size==0) {
                            Util.println("zip size is 0");
                            runOnUiThread(() -> Util.makeText(MainActivity.this, "zip size is 0").show());
                            return;
                        }
                        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();
                        runOnUiThread(() -> {
                            Util.println("Import Succeed");
                            Util.makeText(MainActivity.this, "Import Succeed 导入成功\nDon't shut down within 1 minute, otherwise the program may be lost\n不要在 1 分钟内关机，否则节目可能丢失").show();
                            initProg(json);
                        });
                    } catch (Exception e) {
                        Util.printStackTrace(e);
                        runOnUiThread(() -> Util.makeText(MainActivity.this, Util.toStr(e)).show());
                    }
                }).start();
            }
        }, intentFilter);
        reces.add(rece);

        new Thread(this).start();
//        new Thread(()->{
//            while(true) {
//                try {
//                    Thread.sleep(10000);
//                    if(serviCard!=null && serviCard.asBinder().isBinderAlive()) {
//                        var www = serviCard.getScreenWidth();
//                        var hhh = serviCard.getScreenHeight();
//                        if(www!=Util.screenWidth || hhh!=Util.screenHeight) {
//                            var ins = MainActivity.ins;
//                            if(ins!=null) ins.runOnUiThread(() -> {
//                                Util.screenWidth = www;
//                                Util.screenHeight = hhh;
//                                ins.backView.invalidate();
//                            });
//                        }
//                    } else {
//                        Util.println("  bindService");
//                        var ins = MainActivity.ins;
//                        if(ins!=null) ins.runOnUiThread(() -> ins.bindService(ins.intenCard, ins.connCard, Context.BIND_AUTO_CREATE));
//                    }
//                } catch (Throwable e) {
//                    var ins = MainActivity.ins;
//                    if(ins!=null) ins.runOnUiThread(() -> Util.makeText(ins, Util.toStr(e)).show());
//                    Util.printStackTrace(e);
//                }
//            }
//        }).start();
    }

    public void stopProg() {
        avas.clear();
        curAva = 0;
        curTimes = 1;
        if(insView!=null) {
            insView.release();
            insView = null;
        }
        if(progView!=null) {
            progView.release();
            progView = null;
        }
        setContentView(backView);
        System.gc();
    }
    public boolean delProgFile() {
        stopProg();
        var files = new File(Util.programDir).listFiles();
        var ok = true;
        if(files != null) for(var file : files) if(! file.delete()) ok = false;
        state = 4;
        try {
            var out = new FileOutputStream(Util.programDir+"/program");
            out.write("{}".getBytes());
            out.flush();
            out.getFD().sync();
            out.close();
        } catch (Throwable ignored) {
        }
        return ok;
    }
    public void initProg() {
        state = 1;
        try {
            Util.println("\nParse Insert Prog Json");
            var root = JSMap.fromClose(new BufferedInputStream(new FileInputStream(Util.programDir + "/insert")));
            var task = root.jsmap("task");
            if(task==null && root.containsKey("layers")) task = new JSMap("items", new JSList<>(new JSMap("_program", root)));
            if(task!=null) {
                var view = new Prog(task, this);
                if(view.getChildCount()!=0) setContentView(insView = view);
            }
        } catch(FileNotFoundException ignored) {
        } catch(Throwable e) {
            state = 7;
            Util.printStackTrace(e);
        }
        try {
            Util.println("\nParse Prog Json");
            var root = JSMap.fromClose(new BufferedInputStream(new FileInputStream(Util.programDir + "/program")));
            var task = root.jsmap("task");
            if(task==null && root.containsKey("layers")) task = new JSMap("items", new JSList<>(new JSMap("_program", root)));
            if(task==null) Util.println(root.isEmpty() ? "  Empty program JSON\n" : "  Error: task==null\n");
            else {
                var view = new Prog(task, this);
                if(view.getChildCount()==0) Util.println("  Error: ChildCount==0\n");
                else setContentView(progView = view);
            }
        } catch(FileNotFoundException e) {
            Util.println(""+e);
        } catch(Throwable e) {
            state = 7;
            Util.makeText(this, Util.toStr(e)).show();
            Util.printStackTrace(e);
        }
        if(insView!=null || progView!=null) {
            state = 5;
            Util.println("Init Sync");
            syncProg((System.currentTimeMillis()+999)/1000*1000, 0);
            if(canAdd) {
                choreographer.postFrameCallback(this);
                canAdd = false;
            }
        } else if(state != 7) state = 3;
    }
    public void initProg(byte[] json) {
        try {
            Util.println("\nParse Prog Json");
            var root = JSMap.from(json);
            var task = root.jsmap("task");
            if(task==null) {
                if(! root.containsKey("layers")) {
                    state = 7;
                    Util.println("  Error: task==null\n");
                    Util.println(new String(json, Chsets.UTF8));
                    return;
                }
                task = new JSMap("items", new JSList<>(new JSMap("_program", root)));
            }
            var view = new Prog(task, this);
            if(view.getChildCount()==0) {
                if(! view.isInsert) state = 7;
                Util.println("  Error: ChildCount==0\n");
                Util.println(new String(json, Chsets.UTF8));
                return;
            }
            if(view.isInsert) {
                if(insView!=null) insView.release();
                insView = view;
                setContentView(insView);
            } else {
                if(progView!=null) progView.release();
                progView = view;
                setContentView(progView);
            }
            var fOut = new FileOutputStream(Util.programDir + (view.isInsert?"/insert":"/program"));
            fOut.write(json);
            fOut.flush();
            fOut.getFD().sync();
            fOut.close();
            state = 5;
            System.gc();
            Util.println("Init Sync");
            syncProg((System.currentTimeMillis()+999)/1000*1000, 0);
            if(canAdd) {
                choreographer.postFrameCallback(this);
                canAdd = false;
            }
        } catch (Throwable e) {
            state = 7;
            Util.makeText(this, Util.toStr(e)).show();
            Util.printStackTrace(e);
            Util.println(new String(json, Chsets.UTF8));
        }
    }

    ArrayList<Prog.Page> avas = new ArrayList<>();
    int curAva, curTimes = 1;
    long waitTo = Long.MAX_VALUE;
    boolean isInsert;

    Choreographer choreographer = Choreographer.getInstance();
    boolean canAdd = true;

    @Override
    public void doFrame(long frameTimeNanos) {
        if(progView == null && insView==null) {
            canAdd = true;
            return;
        }
        choreographer.postFrameCallback(this);
        canAdd = false;
        var milli = System.currentTimeMillis();
        if(avas.isEmpty()) {
            if(milli >= waitTo) {
                waitTo = Long.MAX_VALUE;
                Util.println("wait sync");
                syncProg(milli, 0);
                if(insView!=null) for(var call : insView.calls) call.doFrame(milli);
                if(progView!=null) for(var call : progView.calls) call.doFrame(milli);
            }
            return;
        }
        var lastPage = avas.get(curAva);
        if(milli < lastPage.endMilli) lastPage.showHideSrcs(milli);
        else {
            lastPage.hide();
            if(isInsert) {
                if(--lastPage.repeatTimes<=0 && ++curAva >= avas.size()) {
                    var isDiff = milli-lastPage.endMilli>=1000;
                    syncProg(isDiff ? milli : lastPage.endMilli, milli);
                    if(insView!=null) for(var call : insView.calls) call.doFrame(milli);
                    if(progView!=null) for(var call : progView.calls) call.doFrame(milli);
                    return;
                }
            } else {
                if(waitTo > 0) {  //waitTo==0 为点播，不换页
                    if(curTimes < lastPage.repeatTimes) curTimes++;
                    else {
                        curTimes = 1;
                        if(++curAva >= avas.size()) {
                            var isDiff = milli-lastPage.endMilli>=1000;
                            syncProg(isDiff ? milli : lastPage.endMilli, milli);
                            if(insView!=null) for(var call : insView.calls) call.doFrame(milli);
                            if(progView!=null) for(var call : progView.calls) call.doFrame(milli);
                            return;
                        }
                    }
                }
            }
            avas.get(curAva).setMillis(lastPage.endMilli, milli);
            Util.println("curAva: "+curAva+" endMs: "+avas.get(curAva).endMilli);
        }
        if(isInsert) for(var call : insView.calls) call.doFrame(milli);
        else for(var call : progView.calls) call.doFrame(milli);
    }

    void syncProg(long milli, long cur) {
        if(cur==0) cur = milli;
        Util.println("\nSyncProg");
        avas.clear();
        curAva = 0;
        curTimes = 1;
        isInsert = false;
        if(insView!=null) {
            for(int i=0; i<insView.pages.size(); i++) if(insView.pages.get(i).repeatTimes <= 0) {
                for(var layer : insView.pages.get(i).layers) for(var src : layer.srcs) {
                    insView.removeView(src.view);
                    if(src.view instanceof Choreographer.FrameCallback) insView.calls.remove((Choreographer.FrameCallback) src.view);
                }
                insView.pages.remove(i--);
            }
            if(insView.pages.isEmpty()) {
                insView.release();
                insView = null;
                try {
                    var fOut = new FileOutputStream(Util.programDir + "/insert");
                    fOut.write("{}".getBytes());
                    fOut.flush();
                    fOut.getFD().sync();
                    fOut.close();
                } catch(Exception e) {
                    Util.printStackTrace(e);
                }
                System.gc();
            } else {
                var dur = 0;
                for(var page : insView.pages) if(page.isScheOn(milli+dur)) {
                    avas.add(page);
                    dur += page.tDur;
                }
                if(avas.isEmpty()) for(var page : insView.pages) if(page.sches==null) avas.add(page);
                if(! avas.isEmpty()) {
                    isInsert = true;
                    avas.get(curAva).setMillis(milli, cur);
                    if(! insView.isShown()) setContentView(insView);
                    state = 6;
                    Util.println("avas "+avas.size()+" Insert");
                    return;
                }
            }
        }
        if(progView!=null) {
            var dur = 0;
            for(var page : progView.pages) if(page.isScheOn(milli+dur)) {
                avas.add(page);
                dur += page.tDur;
            }
            if(dur==0) for(var page : progView.pages) if(page.sches==null) {
                avas.add(page);
                dur += page.tDur;
            }
            if(dur!=0) {
                var start = milli / dur * dur;
                if(start < milli) {
                    do {
                        start += avas.get(curAva++).tDur;
                    } while(curAva < avas.size() && start<=milli);
                    start -= avas.get(--curAva).tDur;
                    syncMs = milli;
                    Util.println("Sync. dur: "+dur+" milli: "+milli+" start: "+start+" diff: "+(milli - start));
                }
                avas.get(curAva).setMillis(start, milli);
                if(! progView.isShown()) setContentView(progView);
                state = 6;
                Util.println("Avas "+avas.size());
                return;
            }
        }
        waitTo = milli + 1000;
        if(state!=2) {
            setContentView(backView);
            state = 2;
        }
        Util.println("No Avas, back");
    }

    public void run() {
        try {
            var serverSocket = new ServerSocket(3333);
            while(true) {
                try {
                    Util.println("\nAccepting ...");
                    var socket = serverSocket.accept();
                    new SocketThread(MainActivity.ins, socket).start();
                    Util.println("\nAccepted");
                } catch (Throwable e) {
                    var ins = MainActivity.ins;
                    if(ins!=null) ins.runOnUiThread(() -> Util.makeText(ins, Util.toStr(e)).show());
                    Util.printStackTrace(e);
                }
            }
        } catch (Throwable e) {
            var msg = e.getMessage();
            if(msg==null || ! msg.contains("EADDRINUSE")) MainActivity.ins.runOnUiThread(() -> Util.makeText(MainActivity.ins, Util.toStr(e)).show());
            Util.printStackTrace(e);
        }
    }
}