import { Layer, SpriteSheet } from "../applet/Render";
import { Component, copyDense, copySparse, EntityState, StateForSchema, Store } from "./Data";

/**
 * Nominal type used to remind users to enforce some level
 * of quantization on numbers kept in game state. This reduces
 * physics precision in theory, but lets us feel more comfortable
 * about determinism for rollback physics.
 */
export type FixedPoint = number & {_type: "fixed point"};

export function Floor(x: number): FixedPoint {
    return Math.floor(x) as FixedPoint;
}

export class Box {
    constructor(
        public x: FixedPoint, public y: FixedPoint,
        public w: FixedPoint, public h: FixedPoint
    ) { };
};

/**
 * Return source moved towards target by speed, without going past.
 */
export function Approach(source: number, target: number, speed: number): number {
    const delta = target - source;
    if (Math.abs(delta) <= speed) {
        return target;
    } else {
        return source + Math.sign(delta) * speed;
    }
}

/**
 * pairs of vertex coordinates in ccw winding order
 */
export interface Polygon {
    points: FixedPoint[];
}
export class PolygonComponent extends Component<PolygonComponent> {
    points: FixedPoint[];
    constructor(from: Partial<PolygonComponent>) {
        super(from);
        this.points = from.points?.slice() ?? []
    };
    clone() {
        return new PolygonComponent(this);
    }
}

export class Location extends Component<Location> {
    X: FixedPoint;
    Y: FixedPoint;
    Angle: number;
    VX: number;
    VY: number;
    VAngle: number;
    constructor(from: Partial<Location>) {
        super(from);
        this.X = from.X ?? (0 as FixedPoint);
        this.Y = from.Y ?? (0 as FixedPoint);
        this.Angle = from.Angle ?? 0;
        this.VX = from.VX ?? 0;
        this.VY = from.VY ?? 0;
        this.VAngle = from.VAngle ?? 0;
    };
    clone() {
        return new Location(this);
    }
}

export class CollisionClass extends Component<CollisionClass> {
    public name: string;
    constructor(from: Partial<CollisionClass>) {
        super(from);
        this.name = from.name ?? "unknown";
    };
    clone() {
        return new CollisionClass(this);
    }
}

export class RenderBounds extends Component<RenderBounds>  {
    public color: string;
    public layer: number;
    constructor(from: Partial<RenderBounds>) {
        super(from);
        this.color = from.color ?? "#f00";
        this.layer = from.layer ?? 1;
    };
    clone() {
        return new RenderBounds(this);
    }
};

export class RenderSprite extends Component<RenderSprite>  {
    // TODO: make this an id/handle for serializability
    public sheet: SpriteSheet;
    public layer: number;
    public index: number;
    public offsetX: number;
    public offsetY: number;
    constructor(from: Partial<RenderSprite> & { sheet: SpriteSheet }) {
        super(from);
        this.sheet = from.sheet;
        this.layer = from.layer ?? 1;
        this.index = from.index ?? 0;
        this.offsetX = from.offsetX ?? 0;
        this.offsetY = from.offsetY ?? 0;
    };
    clone() {
        return new RenderSprite(this);
    }
};

export interface ComponentSchema {
    location: Location;
    bounds: PolygonComponent;
    renderBounds: RenderBounds;
    renderSprite: RenderSprite;
    collisionSourceClass: CollisionClass;
    collisionTargetClass: CollisionClass;
}

export class Data implements StateForSchema<ComponentSchema> {
    entity: EntityState[];

    location: Store<Location>;
    bounds: Store<PolygonComponent>;
    renderBounds: Store<RenderBounds>;
    renderSprite: Store<RenderSprite>;
    collisionSourceClass: Store<CollisionClass>;
    collisionTargetClass: Store<CollisionClass>;

    layers: Layer[] = [new Layer(0), new Layer(1)];

    constructor(from: Partial<Data>) {
        this.entity = copyDense(from.entity);
        this.location = copyDense(from.location);
        this.bounds = copyDense(from.bounds);
        this.renderBounds = copySparse(from.renderBounds);
        this.renderSprite = copySparse(from.renderSprite);
        this.collisionSourceClass = copySparse(from.collisionSourceClass);
        this.collisionTargetClass = copySparse(from.collisionTargetClass);
    }
    clone() {
        return new Data(this);
    }
}