<commit_before>import { MuWriteStream, MuReadStream } from '../stream';

import { MuSchema } from './schema';
import { isMuPrimitive } from './util/type';

export type _Union<SubTypes extends { [type:string]:MuSchema<any> }> = {
    type:keyof SubTypes;
    data:SubTypes[keyof SubTypes]['identity'];
};

export type _UnionJSON<SubTypes extends { [type:string]:MuSchema<any> }> = {
    type:keyof SubTypes;
    data:any;
};

function isUnion (x) {
    if (x !== Object(x)) {
        return false;
    }
    if (Object.keys(x).length !== 2) {
        return false;
    }

    if (!('type' in x)) {
        return false;
    }
    if (!('data' in x)) {
        return false;
    }

    return true;
}

export class MuUnion<SubTypes extends { [type:string]:MuSchema<any> }>
        implements MuSchema<_Union<SubTypes>> {
    private _types:string[];

    public readonly identity:_Union<SubTypes>;

    public readonly muType = 'union';
    public readonly muData:SubTypes;
    public readonly json:object;

    constructor (
        schemaSpec:SubTypes,
        identityType?:keyof SubTypes,
    ) {
        this._types = Object.keys(schemaSpec);
        this.muData = schemaSpec;

        if (identityType) {
            this.identity = {
                type: identityType,
                data: schemaSpec[identityType].identity,
            };
        } else {
            this.identity = {
                type: '',
                data: void 0,
            };
        }

        const result = {};
        Object.keys(schemaSpec).forEach((subtype) => {
            result[subtype] = this.muData[subtype].json;
        });
        this.json = {
            type: 'union',
            identity: this.identity.type,
            data: result,
        };
    }

    public alloc () : _Union<SubTypes> {
        const type = this.identity.type;
        return {
            type,
            data: type ? this.muData[type].clone(this.identity.data) : void 0,
        };
    }

    public free (union:_Union<SubTypes>) {
        this.muData[union.type].free(union.data);
    }

    public equal (a:_Union<SubTypes>, b:_Union<SubTypes>) {
        if (!isUnion(a) || !isUnion(b)) {
            return false;
        }
        if (a.type !== b.type) {
            return false;
        }
        if (a.type === '') {
            return true;
        }
        return this.muData[a.type].equal(a.data, b.data);
    }

    public clone (union:_Union<SubTypes>) : _Union<SubTypes> {
        const type = union.type;
        return {
            type,
            data: type ? this.muData[type].clone(union.data) : void 0,
        };
    }

    public assign (dst:_Union<SubTypes>, src:_Union<SubTypes>) {
        if (dst === src) {
            return;
        }

        const dType = dst.type;
        const sType = src.type;
        const valueSchema = this.muData;

        dst.type = src.type;

        if (dType !== sType) {
            valueSchema[dType].free(dst.data);
            dst.data = valueSchema[sType].clone(src.data);
            return;
        }

        // same type
        valueSchema[dType].assign(dst.data, src.data);
        if (isMuPrimitive(valueSchema[dType].muType)) {
            dst.data = src.data;
        }
    }

    public diff (
        base:_Union<SubTypes>,
        target:_Union<SubTypes>,
        out:MuWriteStream,
    ) : boolean {
        out.grow(8);

        const trackerOffset = out.offset;
        ++out.offset;

        let tracker = 0;

        const dataSchema = this.muData[target.type];
        if (base.type === target.type) {<commit_msg>schema: assigning empty union doesn't break<commit_after>import { MuWriteStream, MuReadStream } from '../stream';

import { MuSchema } from './schema';
import { isMuPrimitive } from './util/type';

export type _Union<SubTypes extends { [type:string]:MuSchema<any> }> = {
    type:keyof SubTypes;
    data:SubTypes[keyof SubTypes]['identity'];
};

export type _UnionJSON<SubTypes extends { [type:string]:MuSchema<any> }> = {
    type:keyof SubTypes;
    data:any;
};

function isUnion (x) {
    if (x !== Object(x)) {
        return false;
    }
    if (Object.keys(x).length !== 2) {
        return false;
    }

    if (!('type' in x)) {
        return false;
    }
    if (!('data' in x)) {
        return false;
    }

    return true;
}

export class MuUnion<SubTypes extends { [type:string]:MuSchema<any> }>
        implements MuSchema<_Union<SubTypes>> {
    private _types:string[];

    public readonly identity:_Union<SubTypes>;

    public readonly muType = 'union';
    public readonly muData:SubTypes;
    public readonly json:object;

    constructor (
        schemaSpec:SubTypes,
        identityType?:keyof SubTypes,
    ) {
        this._types = Object.keys(schemaSpec);
        this.muData = schemaSpec;

        if (identityType) {
            this.identity = {
                type: identityType,
                data: schemaSpec[identityType].identity,
            };
        } else {
            this.identity = {
                type: '',
                data: void 0,
            };
        }

        const result = {};
        Object.keys(schemaSpec).forEach((subtype) => {
            result[subtype] = this.muData[subtype].json;
        });
        this.json = {
            type: 'union',
            identity: this.identity.type,
            data: result,
        };
    }

    public alloc () : _Union<SubTypes> {
        const type = this.identity.type;
        return {
            type,
            data: type ? this.muData[type].clone(this.identity.data) : void 0,
        };
    }

    public free (union:_Union<SubTypes>) {
        this.muData[union.type].free(union.data);
    }

    public equal (a:_Union<SubTypes>, b:_Union<SubTypes>) {
        if (!isUnion(a) || !isUnion(b)) {
            return false;
        }
        if (a.type !== b.type) {
            return false;
        }
        if (a.type === '') {
            return true;
        }
        return this.muData[a.type].equal(a.data, b.data);
    }

    public clone (union:_Union<SubTypes>) : _Union<SubTypes> {
        const type = union.type;
        return {
            type,
            data: type ? this.muData[type].clone(union.data) : void 0,
        };
    }

    public assign (dst:_Union<SubTypes>, src:_Union<SubTypes>) {
        if (dst === src) {
            return;
        }

        const dType = dst.type;
        const sType = src.type;
        const valueSchema = this.muData;

        dst.type = src.type;

        if (dst.type !== dType) {
            valueSchema[dType] && valueSchema[dType].free(dst.data);
            if (sType) {
                valueSchema[sType] && (dst.data = valueSchema[sType].clone(src.data));
            } else {
                dst.data = void 0;
            }
            return;
        }

        // same type
        if (valueSchema[dType]) {
            valueSchema[dType].assign(dst.data, src.data);
            if (isMuPrimitive(valueSchema[dType].muType)) {
                dst.data = src.data;
            }
        }
    }

    public diff (
        base:_Union<SubTypes>,
        target:_Union<SubTypes>,
        out:MuWriteStream,
    ) : boolean {
        out.grow(8);

        const trackerOffset = out.offset;
        ++out.offset;

        let tracker = 0;

        const dataSchema = this.muData[target.type];
        if (base.type === target.type) {
{'fraction_non_alphanumeric': 0.1338731443994602, 'fraction_numerical': 0.0017543859649122807, 'mean_word_length': 1.7725402169846614, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3351250', 'n_tokens_mistral': 2261, 'n_tokens_neox': 2198, 'n_words': 451}