package com.eloraam.redpower.core;

public class FractalLib {
    public static long hash64shift(long key) {
        key = ~key + (key << 21);
        key ^= key >>> 24;
        key = key + (key << 3) + (key << 8);
        key ^= key >>> 14;
        key = key + (key << 2) + (key << 4);
        key ^= key >>> 28;
        return key + (key << 31);
    }

    public static double hashFloat(long key) {
        long f = hash64shift(key);
        return Double.longBitsToDouble(4607182418800017408L | f & 4503599627370495L)
            - 1.0;
    }

    public static double noise1D(long seed, double pos, float lac, int octave) {
        double tr = 0.5;
        double scale = (double) (1 << octave);

        for (int i = 0; i < octave; ++i) {
            double p = pos * scale;
            long pint = (long) Math.floor(p);
            double m1 = hashFloat(seed + pint);
            double m2 = hashFloat(seed + pint + 1L);
            p -= Math.floor(p);
            double v = 0.5 + 0.5 * Math.cos(Math.PI * p);
            v = v * m1 + (1.0 - v) * m2;
            tr = (double) (1.0F - lac) * tr + (double) lac * v;
            scale *= 0.5;
        }

        return tr;
    }

    public static double perturbOld(long seed, float pos, float lac, int octave) {
        double tr = 0.0;
        double mscale = 1.0;
        double scale = 1.0;

        for (int i = 0; i < octave; ++i) {
            long v = (long) Math.floor((double) pos * scale);
            long p = hash64shift(seed + v);
            double mag
                = Double.longBitsToDouble(4607182418800017408L | p & 4503599627370495L)
                - 1.0;
            tr += mscale * mag * Math.sin(Math.PI * 2 * (double) pos * scale);
            scale *= 2.0;
            mscale *= (double) lac;
        }

        return tr;
    }

    public static void
    fillVector(Vector3 v, Vector3 org, Vector3 dest, float pos, long seed) {
        double window = 4.0 * Math.sin(Math.PI * (double) pos);
        v.x = org.x + (double) (pos * pos) * dest.x
            + window * perturbOld(seed, pos, 0.7F, 5);
        v.y = org.y + (double) pos * dest.y
            + window * perturbOld(seed + 1L, pos, 0.7F, 5);
        v.z = org.z + (double) (pos * pos) * dest.z
            + window * perturbOld(seed + 2L, pos, 0.7F, 5);
    }

    public static int mdist(Vector3 a, Vector3 b) {
        return (int
        ) (Math.abs(Math.floor(a.x) - Math.floor(b.x))
           + Math.abs(Math.floor(a.y) - Math.floor(b.y))
           + Math.abs(Math.floor(a.z) - Math.floor(b.z)));
    }

    public static class BlockRay {
        private Vector3 p1;
        private Vector3 p2;
        private Vector3 dv;
        public Vector3 enter;
        public Vector3 exit;
        public int xp;
        public int yp;
        public int zp;
        public int dir;
        public int face;

        public BlockRay(Vector3 s, Vector3 d) {
            this.p1 = new Vector3(s);
            this.p2 = new Vector3(d);
            this.dv = new Vector3(d);
            this.dv.subtract(s);
            this.exit = new Vector3(s);
            this.enter = new Vector3();
            this.xp = (int) Math.floor(s.x);
            this.yp = (int) Math.floor(s.y);
            this.zp = (int) Math.floor(s.z);
            this.dir = 0;
            this.dir |= d.x > s.x ? 4 : 0;
            this.dir |= d.y > s.y ? 1 : 0;
            this.dir |= d.z > s.z ? 2 : 0;
        }

        public void set(Vector3 s, Vector3 d) {
            this.p1.set(s);
            this.p2.set(d);
            this.dv.set(d);
            this.dv.subtract(s);
            this.exit.set(s);
            this.xp = (int) Math.floor(s.x);
            this.yp = (int) Math.floor(s.y);
            this.zp = (int) Math.floor(s.z);
            this.dir = 0;
            this.dir |= d.x > s.x ? 4 : 0;
            this.dir |= d.y > s.y ? 1 : 0;
            this.dir |= d.z > s.z ? 2 : 0;
        }

        boolean step() {
            double bp = 1.0;
            int sd = -1;
            if (this.dv.x != 0.0) {
                int x = this.xp;
                if ((this.dir & 4) > 0) {
                    ++x;
                }

                double d = ((double) x - this.p1.x) / this.dv.x;
                if (d >= 0.0 && d <= bp) {
                    bp = d;
                    sd = (this.dir & 4) > 0 ? 4 : 5;
                }
            }

            if (this.dv.y != 0.0) {
                int y = this.yp;
                if ((this.dir & 1) > 0) {
                    ++y;
                }

                double d = ((double) y - this.p1.y) / this.dv.y;
                if (d >= 0.0 && d <= bp) {
                    bp = d;
                    sd = (this.dir & 1) > 0 ? 0 : 1;
                }
            }

            if (this.dv.z != 0.0) {
                int z = this.zp;
                if ((this.dir & 2) > 0) {
                    ++z;
                }

                double d = ((double) z - this.p1.z) / this.dv.z;
                if (d >= 0.0 && d <= bp) {
                    bp = d;
                    sd = (this.dir & 2) > 0 ? 2 : 3;
                }
            }

            this.face = sd;
            switch (sd) {
                case 0:
                    ++this.yp;
                    break;
                case 1:
                    --this.yp;
                    break;
                case 2:
                    ++this.zp;
                    break;
                case 3:
                    --this.zp;
                    break;
                case 4:
                    ++this.xp;
                    break;
                case 5:
                    --this.xp;
            }

            this.enter.set(this.exit);
            this.exit.set(this.dv);
            this.exit.multiply(bp);
            this.exit.add(this.p1);
            return bp >= 1.0;
        }
    }

    public static class BlockSnake {
        int fep = -1;
        FractalLib.BlockRay ray;
        Vector3 org;
        Vector3 dest;
        Vector3 fracs;
        Vector3 frace;
        long seed;

        public BlockSnake(Vector3 o, Vector3 d, long s) {
            this.org = new Vector3(o);
            this.dest = new Vector3(d);
            this.fracs = new Vector3(o);
            this.frace = new Vector3();
            this.seed = s;
            FractalLib.fillVector(this.frace, this.org, this.dest, 0.125F, this.seed);
            this.ray = new FractalLib.BlockRay(this.fracs, this.frace);
        }

        public boolean iterate() {
            if (this.fep == -1) {
                ++this.fep;
                return true;
            } else if (!this.ray.step()) {
                return true;
            } else if (this.fep == 8) {
                return false;
            } else {
                this.fracs.set(this.frace);
                FractalLib.fillVector(
                    this.frace, this.org, this.dest, (float) this.fep / 8.0F, this.seed
                );
                this.ray.set(this.fracs, this.frace);
                ++this.fep;
                return true;
            }
        }

        public Vector3 get() {
            return new Vector3(
                (double) this.ray.xp, (double) this.ray.yp, (double) this.ray.zp
            );
        }
    }
}
