/*
 *  MaplyStarModel.java
 *  WhirlyGlobeLib
 *
 *  Created by jmnavarro
 *  Copyright 2011-2022 mousebird consulting
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
package com.mousebird.maply;

import android.app.Activity;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import okio.Okio;


public class MaplyStarModel {

    private class SingleStar
    {
        float mag;
        float ra, dec;
    }

    private static final String vertexShaderTriPoint =
            "uniform mat4  u_mvpMatrix;"+
            "uniform float u_radius;"+
            ""+
            "attribute vec3 a_position;"+
            "attribute float a_size;"+
            ""+
            "varying vec4 v_color;"+
            ""+
            "void main()"+
            "{"+
            "   v_color = vec4(1.0,1.0,1.0,1.0);"+
            "   gl_PointSize = a_size;"+
            "   gl_Position = u_mvpMatrix * vec4(a_position * u_radius,1.0);"+
            "}";

    private static final String fragmentShaderTriPoint =
            "precision lowp float;"+
            ""+
            "varying vec4      v_color;"+
            ""+
            "void main()"+
            "{"+
            "  gl_FragColor = v_color;"+
            "}";

    private static final String fragmentShaderTexTriPoint =
            "precision lowp float;"+
            ""+
            "uniform sampler2D s_baseMap0;"+
            "varying vec4      v_color;"+
            ""+
            "void main()"+
            "{"+
            "  gl_FragColor = v_color * texture2D(s_baseMap0, gl_PointCoord);"+
            "}";

    ArrayList<SingleStar> stars;
    ParticleSystem particleSystem;
    ComponentObject particleSystemObj;
    GlobeController viewC;
    RenderController.ThreadMode addedMode;
    Bitmap image;


    public MaplyStarModel(String fileName, String imageName, Activity activity) throws IOException {
        AssetManager assetMgr = activity.getAssets();
        InputStream inputStream = null;
        String[] paths = assetMgr.list("maplystarmodel");
        for (String path : paths) {
            if (path.equals(imageName)) {
                //image
                BufferedInputStream bufferedInputStream = null;
                try {
                    inputStream = assetMgr.open("maplystarmodel/" + path);
                    bufferedInputStream = new BufferedInputStream(inputStream);
                    image = BitmapFactory.decodeStream(bufferedInputStream);
                } finally {
                    if (bufferedInputStream != null) {
                        try {
                            bufferedInputStream.close();
                        } catch (IOException e) {
                        }
                    }
                }
            }
            if (path.equals(fileName)) {
                //data
                Matcher m;
                try {
                    inputStream = assetMgr.open("maplystarmodel/" + path);
                    String stars = Okio.buffer(Okio.source(inputStream)).readUtf8();
                    Pattern p = Pattern.compile("[-]?[0-9]*\\.?[0-9]+");
                    m = p.matcher(stars);
                } finally {
                    if (inputStream != null) {
                        try {
                            inputStream.close();
                        } catch (IOException e) {
                        }
                    }
                }

                this.stars = new ArrayList<SingleStar>();
                if (m.groupCount() % 3 == 0){
                    int i = 0;
                    SingleStar s = null;
                    while (m.find()) {
                        switch (i) {
                            case 0:
                                s = new SingleStar();
                                s.ra = Float.valueOf(m.group());
                                i++;
                                break;
                            case 1:
                                s.dec = Float.valueOf(m.group());
                                i++;
                                break;
                            case 2:
                                s.mag = Float.valueOf(m.group());
                                this.stars.add(s);
                                i = 0;
                                break;
                            default:
                                break;
                        }
                    }
                }
            }
        }
    }

    public void addToViewc (GlobeController inViewC, RenderController.ThreadMode mode) {
        this.viewC = inViewC;
        this.addedMode = mode;

        //Julian date for position calculation
        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        double jd = getJulianDateDouble(cal.getTimeInMillis());
        double siderealTime = Greenwich_Mean_Sidereal_Deg(jd);

        //Really simple shader
        Shader shader = new Shader("Star Shader", vertexShaderTriPoint, (image != null ? fragmentShaderTexTriPoint : fragmentShaderTriPoint), viewC);
        shader.setUniform("u_radius", 6.0);
        viewC.addShaderProgram(shader);

        //Set up a simple particle system (that doesn't move)
        particleSystem = new ParticleSystem("Stars");
        particleSystem.setParticleSystemType(ParticleSystem.Type.Point);
        particleSystem.setLifetime(1e20);
        particleSystem.setTotalParticles(stars.size());
        particleSystem.setBatchSize(stars.size());
        if (shader != null)
            particleSystem.setRenderShader(shader);

        if (image != null){
            RenderController.TextureSettings texSet = new RenderController.TextureSettings();
            MaplyTexture tex = inViewC.addTexture(image, texSet, RenderController.ThreadMode.ThreadCurrent);
            particleSystem.addTexture(tex);
        }

        particleSystem.addAttribute("a_position", Shader.AttributeType.Float3);
        particleSystem.addAttribute("a_size", Shader.AttributeType.Float);

        particleSystemObj = viewC.addParticleSystem(particleSystem, addedMode);

        //Data arrays for particles
        //We'll clear them out in case we don't fill them out completely
        float[] posData = new float[stars.size()*3];
        float[] sizeData = new float[stars.size()];

        for(int i =0; i < stars.size(); i++) {
            SingleStar singleStar = stars.get(i);

            //Convert the start from equatorial to useable lon/lat
            //Note: Should check this math
            double starLon = Math.toRadians(singleStar.ra - 15*siderealTime);
            double starLat = Math.toRadians(singleStar.dec);

            double z = Math.sin(starLat);
            double rad = Math.sqrt(1.0 - z * z);
            Point3d pt = new Point3d(rad*Math.cos(starLon), rad * Math.sin(starLon), z);

            posData[i*3] = (float) pt.getX();
            posData[i*3+1] = (float)  pt.getY();
            posData[i*3+2] = (float) pt.getZ();
            float mag = (float) (6.0 - singleStar.mag);
            if (mag < 0.0)
                mag = (float) 0.0;
            sizeData[i] = mag;
        }

        //Set up the particle batch
        ParticleBatch batch = new ParticleBatch(particleSystem);
        batch.addAttribute("a_position", posData);
        batch.addAttribute("a_size", sizeData);
        viewC.addParticleBatch(batch, addedMode);

    }

    public void removeFromView() {
        if (particleSystemObj != null)
            viewC.removeObject(particleSystemObj, addedMode);
    }

    public static double Greenwich_Mean_Sidereal_Deg(double mjd) {
        // calculate T
        double T = (mjd-51544.5)/36525.0;

        // do calculation
        double gmst = ( (280.46061837 + 360.98564736629*(mjd-51544.5)) + 0.000387933*T*T - T*T*T/38710000.0) % 360.0;

        // make positive
        if(gmst < 0) {
            gmst += 360.0;
        }

        return gmst;
    } //Greenwich_Mean_Sidereal_Deg


    public static final int SECONDS_IN_DAY = 60 * 60 * 24;

    public static final long MILLIS_IN_DAY = 1000L * SECONDS_IN_DAY;


    public static double getJulianDateDouble(long millis){

        TimeZone timeZone = TimeZone.getDefault();

        millis += timeZone.getOffset(millis);
        int integer = (int) (millis / MILLIS_IN_DAY);
        double fraction = (double) (millis % MILLIS_IN_DAY) / MILLIS_IN_DAY;
        integer += getInteger(2440587, 0.5);
        fraction += getFraction(2451910, 0.5);
        return (double) integer + fraction;
    }

    public static int getInteger(int i, double f){
        int integer = i;
        int fi = (int) f;
        f -= fi;
        integer += fi;
        if (f < 0) {
            integer--;
        }
        return integer;
    }

    public static double getFraction(int i, double f){
        int fi = (int) f;
        f -= fi;
        if (f < 0) {
            f +=1;
        }
        return f;
    }
}
