import codedraw.CodeDraw;

import java.util.NoSuchElementException;

/**
 * A cosmic system that is composed of a central named body (of type 'NamedBodyForcePair')
 * and an arbitrary number of subsystems (of type 'CosmicSystem') in its orbit.
 * This class implements 'CosmicSystem'.
 */
public class HierarchicalSystem implements CosmicSystem, MassiveIterable {

    private final NamedBodyForcePair central;
    private CosmicSystem[] orbit;
    private CosmicSystem[] all;

    /**
     * Initializes this system with a name and a central body.
     */
    public HierarchicalSystem(NamedBodyForcePair central, CosmicSystem... inOrbit) {
        this.central = central;
        this.orbit = inOrbit;
        this.all = new CosmicSystem[this.orbit.length + 1];
        this.all[0] = central;
        System.arraycopy(this.orbit, 0, this.all, 1, this.orbit.length);
    }

    @Override
    public Vector3 getMassCenter() {
        double mass = this.getMass();
        Vector3 massCenter = new Vector3();
        for (CosmicSystem sys : all) {
            massCenter.add(sys.getMassCenter().times(sys.getMass() / mass));
        }
        return massCenter;
    }

    @Override
    public double getMass() {
        double mass = 0;
        for (CosmicSystem sys : all) {
            mass += sys.getMass();
        }
        return mass;
    }

    @Override
    public int numberOfBodies() {
        int num = 0;
        for (CosmicSystem sys : all) {
            num += sys.numberOfBodies();
        }
        return num;
    }

    @Override
    public double distanceTo(CosmicSystem cs) {
        return this.getMassCenter().distanceTo(cs.getMassCenter());
    }

    @Override
    public void addForceFrom(Body b) {
        for (CosmicSystem sys : all) {
            sys.addForceFrom(b);
        }
    }

    @Override
    public void addForceTo(CosmicSystem cs) {
        for (CosmicSystem sys : all) {
            sys.addForceTo(cs);
        }
    }

    @Override
    public BodyLinkedList getBodies() {
        BodyLinkedList list = new BodyLinkedList();
        for (CosmicSystem sys : all) {
            for (Body b : sys.getBodies()) {
                list.addFirst(b);
            }
        }
        return list;
    }

    @Override
    public void update() {
        for (CosmicSystem sys : all) {
            sys.update();
        }
    }

    @Override
    public void draw(CodeDraw cd) {
        for (CosmicSystem sys : all) {
            sys.draw(cd);
        }
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(central.getName());

        sb.append(" {");
        boolean first = true;
        for (CosmicSystem sys : orbit) {
            if (!first) sb.append(", ");
            sb.append(sys.toString());
            first = false;
        }
        sb.append("}");

        return sb.toString();
    }

    /**
     * Puts the system 'cs' at the first place in the orbit of this system.
     * Precondition: cs != null
     */
    public boolean putFirst(CosmicSystem cs) {
        CosmicSystem[] old = orbit;
        orbit = new CosmicSystem[old.length + 1];
        all = new CosmicSystem[old.length + 2];

        orbit[0] = cs;
        System.arraycopy(old, 0, orbit, 1, old.length);
        all[0] = central;
        System.arraycopy(orbit, 0, all, 1, orbit.length);

        return true;
    }

    @Override
    public MassiveIterator iterator() {
        return new MassiveIterator() {
            private int i = 0;
            private MassiveIterator cur = null;

            @Override
            public Massive next() {
                if (!hasNext()) throw new NoSuchElementException();
                if (cur != null && cur.hasNext()) return cur.next();

                while (i < all.length) {
                    CosmicSystem sys = all[i++];
                    if (sys instanceof NamedBodyForcePair m) {
                        return m.getBody();
                    } else if (sys instanceof HierarchicalSystem hs) {
                        cur = hs.iterator();
                        if (cur.hasNext()) {
                            return cur.next();
                        }
                    }
                }

                return null;
            }

            @Override
            public boolean hasNext() {
                if (cur != null && cur.hasNext()) return true;

                while (i < all.length) {
                    CosmicSystem sys = all[i];
                    if (sys instanceof NamedBodyForcePair) return true;
                    i++;

                    if (sys instanceof HierarchicalSystem hs) {
                        cur = hs.iterator();
                        if (cur.hasNext()) {
                            return true;
                        }
                    }
                }

                return false;
            }
        };
    }
}
