import MapGenerator from './map-generator';
import stringify    from './stringify';
import warnOnce     from './warn-once';
import Result       from './result';
import parse        from './parse';

function isPromise(obj) {
    return typeof obj === 'object' && typeof obj.then === 'function';
}

export default class LazyResult {

    constructor(processor, css, opts) {
        this.stringified = false;
        this.processed   = false;

        let root;
        if ( typeof css === 'object' && css.type === 'root' ) {
            root = css;
        } else if ( css instanceof LazyResult || css instanceof Result ) {
            root = css.root;
            if ( css.map && typeof opts.map === 'undefined' ) {
                opts.map = { prev: css.map };
            }
        } else {
            let parser = parse;
            if ( opts.syntax )  parser = opts.syntax.parse;
            if ( opts.parser )  parser = opts.parser;
            if ( parser.parse ) parser = parser.parse;

            try {
                root = parser(css, opts);
            } catch (error) {
                this.error = error;
            }
        }

        this.result = new Result(processor, root, opts);
    }

    get processor() {
        return this.result.processor;
    }

    get opts() {
        return this.result.opts;
    }

    get css() {
        return this.stringify().css;
    }

    get content() {
        return this.stringify().content;
    }

    get map() {
        return this.stringify().map;
    }

    get root() {
        return this.sync().root;
    }

    get messages() {
        return this.sync().messages;
    }

    warnings() {
        return this.sync().warnings();
    }

    toString() {
        return this.css;
    }

    then(onFulfilled, onRejected) {
        return this.async().then(onFulfilled, onRejected);
    }

    catch(onRejected) {
        return this.async().catch(onRejected);
    }

    handleError(error, plugin) {
        try {
            this.error = error;
            if ( error.name === 'CssSyntaxError' && !error.plugin ) {
                error.plugin = plugin.postcssPlugin;
                error.setMessage();
            } else if ( plugin.postcssVersion ) {
                let pluginName = plugin.postcssPlugin;
                let pluginVer  = plugin.postcssVersion;
                let runtimeVer = this.result.processor.version;
                let a = pluginVer.split('.');
                let b = runtimeVer.split('.');

                if ( a[0] !== b[0] || parseInt(a[1]) > parseInt(b[1]) ) {
                    warnOnce(`Your current PostCSS version is ${runtimeVer}, ` +
                             `but ${pluginName} uses ${pluginVer}. Perhaps ` +
                             `this is the source of the error below.`);
                }
            }
        } catch (err) {
            if ( console && console.error ) console.error(err);
        }
    }

    asyncTick(resolve, reject) {
        if (this.plugin >= this.processor.plugins.length) {
            this.processed = true;
            return resolve();
        }

        try {
            let plugin  = this.processor.plugins[this.plugin];
            let promise = this.run(plugin);
            this.plugin += 1;

            if ( isPromise(promise) ) {
                promise.then( () => {
                    this.asyncTick(resolve, reject);
                }).catch( (error) => {
                    this.handleError(error, plugin);
                    this.processed = true;
                    reject(error);
                });
            } else {
                this.asyncTick(resolve, reject);
            }

        } catch (error) {
            this.processed = true;
            reject(error);
        }
    }

    async() {
        if ( this.processed ) {
            return new Promise( (resolve, reject) => {
                if ( this.error ) {
                    reject(this.error);
                } else {
                    resolve(this.stringify());
                }
            });
        }
        if ( this.processing ) {
            return this.processing;
        }

        this.processing = new Promise( (resolve, reject) => {
            if ( this.error ) return reject(this.error);
            this.plugin = 0;
            this.asyncTick(resolve, reject);
        }).then( () => {
            this.processed = true;
            return this.stringify();
        });

        return this.processing;
    }

    sync() {
        if ( this.processed ) return this.result;
        this.processed = true;

        if ( this.processing ) {
            throw new Error(
                'Use process(css).then(cb) to work with async plugins');
        }

        if ( this.error ) throw this.error;

        for ( let plugin of this.result.processor.plugins ) {
            let promise = this.run(plugin);
            if ( isPromise(promise) ) {
                throw new Error(
                    'Use process(css).then(cb) to work with async plugins');
            }
        }

        return this.result;
    }

    run(plugin) {
        this.result.lastPlugin = plugin;

        try {
            return plugin(this.result.root, this.result);
        } catch (error) {
            this.handleError(error, plugin);
            throw error;
        }
    }

    stringify() {
        if ( this.stringified ) return this.result;
        this.stringified = true;

        this.sync();

        let opts = this.result.opts;
        let str  = stringify;
        if ( opts.syntax )      str = opts.syntax.stringify;
        if ( opts.stringifier ) str = opts.stringifier;
        if ( str.stringify )    str = str.stringify;

        let map  = new MapGenerator(str, this.result.root, this.result.opts);
        let data = map.generate();
        this.result.css = data[0];
        this.result.map = data[1];

        return this.result;
    }

}

@@ -2973,16 +2973,17 @@
     if (
+ 
 this.plu
@@ -3018,16 +3018,17 @@
 s.length
+ 
 ) %7B%0A    

Fix code style