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.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;
    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);
        var msg = "==== MainActivity onCreate ==== UI Thread: "+Thread.currentThread().getId();
        Util.println(msg);
        ins = this;
        startService(new Intent(this, RestartService.class));

        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 ...");
            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(requestCode==999 && grantResults.length > 0 && grantResults[0]==PackageManager.PERMISSION_GRANTED && backView==null) init();
    }

    ConnService connService;

    @SuppressLint("UnspecifiedRegisterReceiverFlag")
    public void init() {
        var dir = Build.VERSION.SDK_INT < Build.VERSION_CODES.R ? Environment.getExternalStorageDirectory().getAbsolutePath() + "/XixunPlayer" : getExternalFilesDir(null).getAbsolutePath();
        var msg = "---- dir "+dir;
        Util.println(msg);
        Util.programDir = dir + "/program";
        Util.backImgFile = dir + "/background";

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

        var cardConn = new ServiceConnection() {
            public void onServiceDisconnected(ComponentName name) {
                Util.println("Disconnected cardsystem aidl service");
            }

            public void onServiceConnected(ComponentName name, IBinder iBinder) {
                unbindService(this);
                Util.println("Bind cardsystem aidl service success");
                var service = CardService.Stub.asInterface(iBinder);
                try {
                    Util.isScreenOn = service.isScreenOpen();
                    Util.screenWidth = service.getScreenWidth();
                    Util.screenHeight = service.getScreenHeight();
                    Util.println("  IsScreenOn: "+Util.isScreenOn+" screen: "+Util.screenWidth+" x "+Util.screenHeight);
                    setContentView(backView = new BackView(MainActivity.this, Util.screenWidth, Util.screenHeight));
                    state = 5;
                    if(Util.isScreenOn) initProg();
                    else state = 8;
                } catch (Exception e) {
                    Util.makeText(MainActivity.this, Util.toStr(e)).show();
                    Util.printStackTrace(e);
                }
            }
        };
        var intent = new Intent("com.xixun.joey.aidlset.SettingsService");
        intent.setPackage("com.xixun.joey.cardsystem");
        bindService(intent, cardConn, Context.BIND_AUTO_CREATE);

        var connConn = new ServiceConnection() {
            public void onServiceDisconnected(ComponentName name) {
                Util.println("Disconnected xy.conn aidl service");
                connService = null;
            }
            public void onServiceConnected(ComponentName name, IBinder iBinder) {
                unbindService(this);
                Util.println("Bind xy.conn aidl service success");
                connService = ConnService.Stub.asInterface(iBinder);
                try {
                    Util.serverURL = connService.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);
                }
            }
        };
        intent = new Intent("xixun.intent.action.CONNECTION_INFO");
        intent.setPackage("com.xixun.xy.conn");
        bindService(intent, connConn, Context.BIND_AUTO_CREATE);

        registerReceiver(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) initProg();
            }
        }, new IntentFilter("com.xixun.action.PAUSE_PLAYER"));

        registerReceiver(new BroadcastReceiver(){
            @Override
            public void onReceive(Context context, Intent intent) {
                Util.println("Receive CHANGE_COMPANYID");
                if(connService!=null) {
                    try {
                        Util.serverURL = connService.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);
                    }
                }
            }
        }, new IntentFilter("com.xixun.joey.CHANGE_COMPANYID"));

        registerReceiver(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"));

        registerReceiver(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;
                    if(! progView.avas.isEmpty()) {
                        progView.curAva().hide();
                        if(progView.avas.size()==1 && progView.avas.get(0)==code-1) for(var call : progView.calls) call.doFrame(System.currentTimeMillis());
                    }
                    var millis = (System.currentTimeMillis()+999)/1000*1000;
                    if(code > 0) {
                        progView.avas.clear();
                        progView.avas.add(code-1);
                        progView.curAva = 0;
                        progView.curTimes = 1;
                        progView.waitTo = 0; //点播
                        var page = progView.curAva();
                        page.setMillis(millis);
                        if(state != 6) {
                            setContentView(progView);
                            state = 6;
                        }
                    } else {
                        progView.waitTo = Long.MAX_VALUE;
                        syncProg(millis);
                    }
                } catch (Throwable e) {
                    Util.makeText(MainActivity.this, Util.toStr(e)).show();
                    Util.printStackTrace(e);
                }
            }
        }, new IntentFilter("com.xixun.yzd.REMOTE_CONTROL"));

        var intentFilter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
        intentFilter.addDataScheme("file");
        registerReceiver(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);

        new Thread(this).start();
    }

    public void stopProg() {
        if(insView!=null) {
            insView.release();
            insView = null;
        }
        if(progView!=null) {
            progView.release();
            progView = null;
        }
        setContentView(backView);
    }
    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);
            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;
            Util.println("Init Sync");
            syncProg((System.currentTimeMillis()+999)/1000*1000);
            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));
        }
    }

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

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

    void syncProg(long milli) {
        if(insView!=null) {
            insView.avas.clear();
            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);
                    insView.calls.remove(src);
                }
                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);
                }
            } else {
                var dur = 0;
                for(int i=0; i<insView.pages.size(); i++) if(insView.pages.get(i).isScheOn(milli+dur)) {
                    insView.avas.add(i);
                    dur += insView.pages.get(i).tDur;
                }
                if(dur==0) for(int i=0; i<insView.pages.size(); i++) if(insView.pages.get(i).sches==null) {
                    insView.avas.add(i);
                    dur += insView.pages.get(i).tDur;
                }
                if(dur!=0) {
                    insView.curAva = 0;
                    insView.curAva().setMillis(milli);
                    if(! insView.isShown()) setContentView(insView);
                    state = 6;
                    return;
                }
            }
        }
        if(progView!=null) {
            progView.curTimes = 1;
            progView.avas.clear();
            var dur = 0;
            for(int i=0; i<progView.pages.size(); i++) if(progView.pages.get(i).isScheOn(milli+dur)) {
                progView.avas.add(i);
                dur += progView.pages.get(i).tDur;
            }
            if(dur==0) {
                for(int i=0; i<progView.pages.size(); i++) if(progView.pages.get(i).sches==null) {
                    progView.avas.add(i);
                    dur += progView.pages.get(i).tDur;
                }
                if(dur==0) {
                    progView.waitTo = milli + 1000;
                    if(state!=2) {
                        setContentView(backView);
                        state = 2;
                    }
                    return;
                }
            }
            var start = milli / dur * dur;
            progView.curAva = 0;
            if(start < milli) {
                do {
                    start += progView.curAva().tDur;
                    progView.curAva++;
                } while(progView.curAva < progView.avas.size() && start<=milli);
                progView.curAva--;
                start -= progView.curAva().tDur;
                syncMs = milli;
                Util.println("Sync. dur: "+dur+" milli: "+milli+" start: "+start+" diff: "+(milli - start));
            }
            progView.curAva().setMillis(start);
            if(! progView.isShown()) setContentView(progView);
            state = 6;
        }
    }

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