/**
 * Created by rburson on 3/18/16.
 */

import * as React from 'react'
import {
    CvAction,
    CvActionCallback,
    CvActionFiredResult,
    CvState,
    CvProps,
    CvBaseMixin,
    CvContext,
    CvNavigationResult,
    CvDetailsPane,
    CvDetailsPaneCallback,
    CvRecord,
    CvStateChangeResult,
    CvEvent,
    CvValueProvider,
    CvActionHandlerParams,
    ColorUtil,
    DateUtil, CvValueListener,
    CvImagePackageDelete, CvImagePackagePick, CvImagePackageUndo, CvImageProducer
} from 'catreact'
import {
    CvAttributeCellValueDef,
    CvCellValueDef,
    CvDataAnno,
    CvImage,
    CvDetailsPanelControls,
    CvDetailsPanelCommitCancelControls
} from './catreact-html'
import {
    FormContext,
    EntityRec,
    CellValueDef,
    CellDef,
    CodeRef,
    LabelCellValueDef,
    PrintMarkupContext, PrintMarkupDef, XMLHttpClient, Try, Log, TextAlignment, AttributeCellValueDef, Binding, Button,
    TimeValue,
    DetailsContext, ObjUtil, Edges, Checkbox, Prop, PropDef
} from 'catavolt-sdk'

import {
    Button as PTButton,
    Cell,
    Color,
    Component as PTComponent,   // PT = PrintType
    Container,
    DatePicker,
    Grid,
    GridLine,
    Form,
    Image as PTImage,
    Label,
    Layout,
    Page,
    RichNum,
    SignatureCapture,
    TextAttributes,
    TextArea,
    TimePicker
} from 'catavolt-sdk'

import Component = React.Component;
import ReactChild = React.ReactChild;
import ReactElement = React.ReactElement;
import ComponentClass = React.ComponentClass;
import {openSignaturePopup} from "./CvSignaturePopup";
import {
    CvImageDefaultAddButtonClass, CvImageDefaultRemoveButtonClass, CvImageDefaultSignatureButtonClass,
    CvImageDefaultUndoButtonClass
} from "./CvImage";
import {CSSProperties} from "react";


export interface CvPrintPanelState extends CvState {
}

/**
 * Details Panel Properties
 */
export interface CvPrintPanelProps extends CvProps {
    formContext?:FormContext;
    paneRef?:number;
    printMarkupContext?:PrintMarkupContext;
    navigationListeners?:Array<(event:CvEvent<CvNavigationResult>)=>void>;
    stateChangeListeners?:Array<(event:CvEvent<CvStateChangeResult>)=>void>;
    actionListeners?:Array<(event:CvEvent<CvActionFiredResult>)=>void>;
    actionProvider?:CvValueProvider<CvActionHandlerParams>;
}

interface LineProps { top?:String, left?:String, length?:String, borderStyle?:String, thickness?:String, color?:String }
interface LineState { }

interface BaseFormState { }

interface Listeners {
    navigationListeners?:Array<(event:CvEvent<CvNavigationResult>)=>void>;
    stateChangeListeners?:Array<(event:CvEvent<CvStateChangeResult>)=>void>;
    actionListeners?:Array<(event:CvEvent<CvActionFiredResult>)=>void>;
}


interface PositionProps { height:number, width:number, left:number, top:number }
interface PrintProps extends CvProps { printBundle:PrintBundle, positionProps:PositionProps };
class PrintBundle {
    public radioGroupRegistry = new RadioGroupRegistry();
    constructor(
        public printMarkupDef:PrintMarkupDef,
        public printMarkupContext:PrintMarkupContext,
        public detailsCallback:CvDetailsPaneCallback,
        public listeners:Listeners) {}
}

class RadioGroupRegistryEntry {
    constructor(public value:any, public cb:()=>void) {};
}
class RadioGroupRegistry {
    private _controls = {};
    public addControl(radioGroup:string, value:any, cb:()=>void) {
        let existing:Array<RadioGroupRegistryEntry> = this._controls[radioGroup];
        if (!existing) {
            existing = new Array<RadioGroupRegistryEntry>();
        }
        // If there is already an entry for <value> then drop it out.
        existing = existing.filter(rge=>(rge.value != value));
        // Add control to registry.
        existing.push(new RadioGroupRegistryEntry(value, cb));
        this._controls[radioGroup] = existing;
    }
    public forceUpdate(radioGroup:string):void {
        let existing:Array<RadioGroupRegistryEntry> = this._controls[radioGroup];
        if (existing) {
            existing.forEach(rge=>(rge.cb()));
        }
    }
}

function hasInitialFocus(context:PrintMarkupContext, propName:string) {
    return context.printMarkupDef.focusPropName ?
        propName == context.printMarkupDef.focusPropName : false;
}


///////////////////////////////////////////////////////////////////////////////
// Form
///////////////////////////////////////////////////////////////////////////////
interface PFormProps { printBundle:PrintBundle }
class PForm extends React.Component<PFormProps, BaseFormState> {
    public static pageSeparatorHeight:number=35;

    childrenFromModel(): Array<any> {
        const bundle = this.props.printBundle;
        const form=bundle.printMarkupDef.printMarkupModel;
        let answer=new Array<any>();
        let pagePP = {height:form.layout.singleHeightNum(), width:form.layout.singleWidthNum(), left:0, top:0 };
        let separatorPP = {height:PForm.pageSeparatorHeight, width:1, left:0, top:0 };
        for (let i:number=0; i < form.pageChildren.length; i++) {
            let p:Page=form.pageChildren[i];
            answer.push(<PPage key={p.id} printBundle={bundle} page={p} positionProps={pagePP} backgroundColor={form.backgroundColor}/>);
            if (i<form.pageChildren.length-1) {
                answer.push(<PPageSeparator key={"GenSep_"+i} printBundle={bundle}
                                            positionProps={separatorPP} />);
            }
        }
        return answer;
    }

    render() {
        const form=this.props.printBundle.printMarkupDef.printMarkupModel;
        const formHeight = form.layout.singleHeightNum() * form.pageChildren.length + (form.pageChildren.length-1) * PForm.pageSeparatorHeight;
        const style:CSSProperties={position:"relative", height: formHeight, width:form.layout.singleWidthNum(), marginLeft:5, marginBottom:5};
        const styleCentered={height: formHeight, width:form.layout.singleWidthNum()};
        return <div style={style}>
            {this.childrenFromModel()}
        </div>
    }
}
///////////////////////////////////////////////////////////////////////////////
// Page
///////////////////////////////////////////////////////////////////////////////
interface PPageProps extends PrintProps { page:Page, backgroundColor:Color }
class PPage extends React.Component<PPageProps, BaseFormState> {
    render() {
        let style = CvPrintUtil.applySizeAndLocationStyle(this.props.positionProps, {backgroundColor:ColorUtil.toColor(this.props.backgroundColor)});

        return <div style={style} >
            { CvPrintUtil.gridsAsChildren(this.props.printBundle, this.props.page.gridChildren, this.props.positionProps.width) }
        </div>
    }
}
///////////////////////////////////////////////////////////////////////////////
// Page Separator
///////////////////////////////////////////////////////////////////////////////
class PPageSeparator extends React.Component<PrintProps, BaseFormState> {
    render() {
        let style = { height:this.props.positionProps.height, width:this.props.positionProps.width}

        return <div style={style}/>
    }
}
///////////////////////////////////////////////////////////////////////////////
// Grid
///////////////////////////////////////////////////////////////////////////////
interface GridProps extends PrintProps { grid:Grid, parentSize:number }
class PGrid extends React.Component<GridProps, BaseFormState> {

    childrenFromModel(props:GridProps): Array<any> {
        // The top is a little tricky.  Keep a running total of all child heights added.  Subtracting this gets
        // our cell to the top.  Then add in a running total based on the row we are working on.
        let allAddedHeights=0;
        let currentRowOffset=0;
        let prevTop:number=0;
        let answer:Array<any>=[];
        let row=0;
        let grid:Grid=this.props.grid;
        grid.layout.heights.map(function(hrn) {
            let nextLeft:number=0;
            let height=hrn.resolveWithFill(props.parentSize);
            let col=0;
            grid.layout.widths.map(function(wrn) {
                let c=grid.cellChildren[row][col];
                let width = wrn.resolveWithFill(props.parentSize);
                let myTop=currentRowOffset-allAddedHeights;
                let pp = {height:height, width:width, left:nextLeft, top:myTop }
                answer.push(<PCell key={c.id} printBundle={props.printBundle} cell={c} positionProps={pp}/>);
                nextLeft+=width;
                col++;
            });
            currentRowOffset+=height;
            row++;
        });
        return answer;
    }
    render() {
        let style = CvPrintUtil.applySizeAndLocationStyle(this.props.positionProps, {});
        return <div style={style} >
            {this.childrenFromModel(this.props)}
        </div>
    }
}
///////////////////////////////////////////////////////////////////////////////
// Cell
///////////////////////////////////////////////////////////////////////////////

interface CellProps extends PrintProps { cell:Cell }
class PCell extends React.Component<CellProps, BaseFormState> {
    render() {
        let style = CvPrintUtil.applySizeAndLocationStyle(this.props.positionProps, {});
        if (this.props.cell.backgroundColor) {
            style["backgroundColor"]=ColorUtil.toColor(this.props.cell.backgroundColor);
        }

        return <div style={style} >
            {CvPrintUtil.gridsAsChildren(this.props.printBundle, this.props.cell.gridChildren, this.props.cell.cellWidth)}
            {CvPrintUtil.componentsAsChildren(this.props.printBundle, this.props.cell.componentChildren, this.props.cell.cellWidth, this.props.cell.cellHeight)}
        </div>
    }
}

///////////////////////////////////////////////////////////////////////////////
// GridLines
///////////////////////////////////////////////////////////////////////////////
interface GridLinesProps extends PrintProps { grid:Grid }
class PGridLines extends React.Component<GridLinesProps, BaseFormState> {
    private OVERFLOW=5;
    componentDidMount() {
        this.updateCanvas();
    }
    updateCanvas():void {

        let canvas:HTMLCanvasElement = this.refs["mycanvas"] as HTMLCanvasElement;
        // var canvas:HTMLCanvasElement = document.getElementById("mycanvas") as HTMLCanvasElement;
        let ctx:CanvasRenderingContext2D = canvas.getContext("2d");
        ctx.globalCompositeOperation = 'source-over';
        ctx.strokeStyle = "black";

        for (let gl of this.props.grid.gridLines) {
            ctx.beginPath();
            ctx.lineWidth = gl.lineWidth;
            ctx.lineCap = "square";
            ctx.moveTo(gl.start.x+this.OVERFLOW, gl.start.y+this.OVERFLOW);
            ctx.lineTo(gl.end.x+this.OVERFLOW, gl.end.y+this.OVERFLOW);
            ctx.stroke();
        }
    }
    render() {
        // Adjust the positionProps to account for the overflow.  The overlfow allows the lines to exceed the bounds.
        let newPP = { height:this.props.positionProps.height+this.OVERFLOW*2, width:this.props.positionProps.width+this.OVERFLOW*2,
            left:this.props.positionProps.left-this.OVERFLOW, top:this.props.positionProps.top-this.OVERFLOW}
        let style = CvPrintUtil.applySizeAndLocationStyle(newPP, {pointerEvents:"none"});
        return <div style={style}>
            <canvas ref="mycanvas" height={newPP.height} width={newPP.width} />
        </div>
    }
}

//*****************************************************************************
// Components
//*****************************************************************************

///////////////////////////////////////////////////////////////////////////////
// Button
///////////////////////////////////////////////////////////////////////////////
interface ButtonProps extends PrintProps { button:PTButton }
class PButton extends React.Component<ButtonProps, BaseFormState> {
    render() {
        let style = CvPrintUtil.applySizeAndLocationStyle(this.props.positionProps, {cursor:"pointer"});
        if (!this.props.button.urlString) return null;
        const path = this.props.button.binding.path;
        const leader = "catavolt:action:name:";
        let actionID = "";
        if (path && path.length > leader.length) {
            actionID = path.substr(leader.length);
        }

        return <CvAction actionId={actionID}
                         paneContext={this.props.printBundle.printMarkupContext}
                         navigationListeners={this.props.printBundle.listeners.navigationListeners}
                         actionListeners={this.props.printBundle.listeners.actionListeners}
                         stateChangeListeners={this.props.printBundle.listeners.stateChangeListeners}>
            <CvImage style={style} src={this.props.button.urlString} />
        </CvAction>
    }

}

///////////////////////////////////////////////////////////////////////////////
// Checkbox
///////////////////////////////////////////////////////////////////////////////
interface CheckboxProps extends PrintProps { checkbox:Checkbox }
interface CheckboxState extends BaseFormState { radioGroupChanged:number }
class PCheckbox extends React.Component<CheckboxProps, CheckboxState> {
    canvasFromRef:HTMLCanvasElement;
    constructor(props) {
        super(props);
        this.state = {radioGroupChanged: 0};
    }
    componentDidUpdate() {
        this.updateCanvas(this.canvasFromRef);
    }
    updateCanvas(canvasParm:HTMLCanvasElement):void {
        this.canvasFromRef = canvasParm;
        if (this.canvasFromRef) {
            // var canvas:HTMLCanvasElement = document.getElementById("mycanvas") as HTMLCanvasElement;
            let ctx: CanvasRenderingContext2D = this.canvasFromRef.getContext("2d");
            ctx.globalCompositeOperation = 'source-over';
            ctx.strokeStyle = "black";

            let h:number = this.canvasFromRef.height;
            let w:number = this.canvasFromRef.width;
            ctx.clearRect(0,0,w,h);

            // Fill with appropriate color
            if (this._isSeleced()) {
                ctx.fillStyle = ColorUtil.toColor(this.props.checkbox.checkedColor);
            } else {
                ctx.fillStyle = ColorUtil.toColor(this.props.checkbox.uncheckedColor);
            }
            ctx.fillRect(0,0,w,h);

            // Draw the framing outline if there is a lineWidth
            if (this.props.checkbox.lineWidth > 0) {
                ctx.beginPath();
                ctx.lineWidth = this.props.checkbox.lineWidth;
                ctx.lineCap = "square";
                ctx.moveTo(0, 0);
                ctx.lineTo(0, h);
                ctx.lineTo(w, h);
                ctx.lineTo(w, 0);
                ctx.lineTo(0, 0);
                if (this._isSeleced()) {
                    ctx.moveTo(0, 0);
                    ctx.lineTo(w, h);
                    ctx.moveTo(0, h);
                    ctx.lineTo(w, 0);
                }
                ctx.stroke();
            }
        }
    }
    _isSeleced() {
        const b = this._binding();
        if (b.propertyName) {
            var prop: Prop = this.props.printBundle.printMarkupContext.buffer.propAtName(b.propertyName);
            var propDef: PropDef = this.props.printBundle.printMarkupContext.entityRecDef.propDefAtName(b.propertyName);
        }
        if (!prop) return false;   // To be called again when there is data

        let pv = (prop.value instanceof CodeRef) ? (prop.value as CodeRef).code : prop.value;
        let tv = this._trueValue();
        return (tv == pv);
    }
    _trueValue() {
        return this._binding().hasConstant ? this._binding().constantValue : true
    }

    render() {
        const b = this._binding();
        if (b.hasConstant) {
            this.props.printBundle.radioGroupRegistry.addControl(this.props.checkbox.radioGroup, this._trueValue(),
                ()=>{ this._forceRefresh()});
        }
        if (b.propertyName) {
            var prop: Prop = this.props.printBundle.printMarkupContext.buffer.propAtName(b.propertyName);
            var propDef: PropDef = this.props.printBundle.printMarkupContext.entityRecDef.propDefAtName(b.propertyName);
        }
        let style = CvPrintUtil.applySizeAndLocationStyle(this.props.positionProps, {});
        style = CvPrintUtil.applyPaddingStyleToSizeAndLoc(this.props.checkbox.padding, style);
        if (!this._isReadOnly(prop)) {
            style["cursor"] = "pointer";
        }
        return <div style={style} onClick={this._checkboxClick.bind(this, prop, propDef)}>
            <canvas ref={(c:HTMLCanvasElement)=>{ this.updateCanvas(c)} }
                    height={style.height} width={style.width} />
        </div>
    }
    _forceRefresh():void {
        let current:number = this.state && !isNaN(this.state.radioGroupChanged) ? this.state.radioGroupChanged : 0;
        this.setState( {radioGroupChanged: ++current} );
    }
    _binding (): Binding {
        return (this.props.checkbox as PTComponent).binding;
    }
    _checkboxClick (prop: Prop, propDef: PropDef, e: any) {
        if (!this._isReadOnly(prop)) {
            let cb: CvDetailsPaneCallback = this.props.printBundle.detailsCallback;
            if (!this._isSeleced()) {
                // if(this.props.valueChangeListener) this.props.valueChangeListener(propName, propDef, true);
                cb.setPropValue(prop.name, this._trueValue());
            } else {
                // if(this.props.valueChangeListener) this.props.valueChangeListener(propName, propDef, false);
                if (!this._binding().hasConstant) {
                    cb.setPropValue(prop.name, false);
                }
            }
            if (this._binding().hasConstant) {
                this.props.printBundle.radioGroupRegistry.forceUpdate(this.props.checkbox.radioGroup);
            } else {
                this.setState( {radioGroupChanged: this.state.radioGroupChanged + 1} );
                // this.updateCanvas();
            }
        }
    }
    _isReadOnly(prop:Prop):boolean {
        return prop ? this.props.printBundle.printMarkupContext.isReadModeFor(prop.name) : true;
    }
};

///////////////////////////////////////////////////////////////////////////////
// Date
///////////////////////////////////////////////////////////////////////////////
interface DateProps extends PrintProps { datePicker:DatePicker }
class PDatePicker extends React.Component<DateProps, BaseFormState> {
    render() {
        let style = CvPrintUtil.applySizeAndLocationStyle(this.props.positionProps, {});
        style = CvPrintUtil.applyTextAttributes(this.props.positionProps.height, this.props.datePicker.textAttributes, style);
        style = CvPrintUtil.applyPaddingStyle(this.props.datePicker.padding, style);

        const b: Binding = this.props.datePicker.binding;
        let prop: Prop = this.props.printBundle.printMarkupContext.buffer.propAtName(b.propertyName);
        let propDef: PropDef = this.props.printBundle.printMarkupContext.entityRecDef.propDefAtName(b.propertyName);
        if (!prop) return null;
        let isReadOnly = this.props.printBundle.printMarkupContext.isReadModeFor(prop.name);
        let val:string = null;
        if (prop.value) {
            // Build the ISO string because toISOString() will adjust for timezone, which we don't want.
            val = DateUtil.toISOFormatNoOffset(prop.value);
        }
        const focusFn = (ref)=>{ if(ref && hasInitialFocus(this.props.printBundle.printMarkupContext, prop.name)) ref.focus(); }
        return <input id={this.props.datePicker.id}
                      type="date"
                      className={"cv-print-control"}
                      style={style}
                      readOnly={isReadOnly}
                      onBlur={this._datePickerChange.bind(this, prop, propDef)}
                      defaultValue={val}
                      ref={focusFn}/>
    }
    _datePickerChange (prop: Prop, propDef: PropDef, e: any) {
        if (prop) {
            let cb: CvDetailsPaneCallback = this.props.printBundle.detailsCallback;
            cb.setPropValue(prop.name, e.currentTarget.value);
        }
    }

}

///////////////////////////////////////////////////////////////////////////////
// Image
///////////////////////////////////////////////////////////////////////////////
interface ImageProps extends PrintProps { image:PTImage }
class PImage extends React.Component<ImageProps, BaseFormState> {
    render() {
        let style = CvPrintUtil.applySizeAndLocationStyle(this.props.positionProps, {});
        if (!this.props.image.urlString) return null;
        return <CvImage
            style={style}
            src={this.props.image.urlString}/>
    }
}

///////////////////////////////////////////////////////////////////////////////
// Label
///////////////////////////////////////////////////////////////////////////////
interface LabelProps extends PrintProps { label:Label }
class PLabel extends React.Component<LabelProps, BaseFormState> {
    render() {
        let style:CSSProperties = {wordWrap: "break-word", wordBreak: "keep-all", textOverflow:"clip", overflow:"hidden" };
        style = CvPrintUtil.applySizeAndLocationStyle(this.props.positionProps, style);
        style["pointerEvents"] = "none";  // Labels may be on top of checkboxes
        style = CvPrintUtil.applyTextAttributes(this.props.positionProps.height, this.props.label.textAttributes, style);
        style = CvPrintUtil.applyPaddingStyle(this.props.label.padding, style);

        return <p key={this.props.label.id} style={style}>{this.props.label.text}</p>
    }
}

///////////////////////////////////////////////////////////////////////////////
// Signature
///////////////////////////////////////////////////////////////////////////////
interface SignatureProps extends PrintProps { signature:SignatureCapture, attributeCellValueDef:AttributeCellValueDef }
interface SignatureState extends BaseFormState { imageListener: CvValueListener<Object> }
class PSignatureCapture extends React.Component<SignatureProps, SignatureState> {
    render() {

        let style = CvPrintUtil.applySizeAndLocationStyle(this.props.positionProps);
        const b = this.props.signature.binding;
        const readOnly = this.props.printBundle.printMarkupContext.isReadModeFor(b.propertyName);
        const detailsContext = this.props.printBundle.printMarkupContext;
        const detailsCallback = this.props.printBundle.detailsCallback;
        const emptyClassSelector = (propDef:PropDef, defaultClass?:string):string => { return "cv-print-control" }
        const hasFocus = hasInitialFocus(this.props.printBundle.printMarkupContext, b.propertyName);
        let imageProducers:Array<CvImageProducer> = null;
        if (!readOnly) {
            imageProducers = [
                {
                    prodImageCB: (listener: CvValueListener<Object>)=> {
                        openSignaturePopup(listener, this.props.signature, {width: style.width, height: style.height});
                    },
                    buttonClassName: CvImageDefaultSignatureButtonClass, includeWhenImageisPresent: true, includeWhenImageIsMissing: true,
                    includeWhenImageIsDeleted: true
                }
            ];

        }

        return <CvAttributeCellValueDef detailsContext={detailsContext} detailsCallback={detailsCallback}
                                        classSelector={emptyClassSelector} cellValueDef={this.props.attributeCellValueDef} key={this.props.signature.id}
                                        imageProducers={imageProducers} inlineStyle={style} imageReadOnly={readOnly} hasFocus={hasFocus}/>

    }
}


///////////////////////////////////////////////////////////////////////////////
// Time
///////////////////////////////////////////////////////////////////////////////
interface TimeProps extends PrintProps { timePicker:TimePicker }
class PTimePicker extends React.Component<TimeProps, BaseFormState> {
    render() {
        let style = CvPrintUtil.applySizeAndLocationStyle(this.props.positionProps, {});
        style = CvPrintUtil.applyTextAttributes(this.props.positionProps.height, this.props.timePicker.textAttributes, style);
        style = CvPrintUtil.applyPaddingStyle(this.props.timePicker.padding, style);

        const b: Binding = this.props.timePicker.binding;
        let prop: Prop = this.props.printBundle.printMarkupContext.buffer.propAtName(b.propertyName);
        let propDef: PropDef = this.props.printBundle.printMarkupContext.entityRecDef.propDefAtName(b.propertyName);
        if (!prop) return null;
        let isReadOnly = this.props.printBundle.printMarkupContext.isReadModeFor(prop.name);
        let val:string = null;
        if (prop.value) {
            val = (prop.value as TimeValue).toDateValue().toTimeString().substr(0, 8);
        }
        const focusFn = (ref)=>{ if(ref && hasInitialFocus(this.props.printBundle.printMarkupContext, prop.name)) ref.focus(); }
        return <input id={this.props.timePicker.id}
                      type="time"
                      className={"cv-print-control"}
                      style={style}
                      readOnly={isReadOnly}
                      onBlur={this._datePickerChange.bind(this, prop, propDef)}
                      defaultValue={val}
                      ref={focusFn}/>
    }
    _datePickerChange (prop: Prop, propDef: PropDef, e: any) {
        if (prop) {
            let cb: CvDetailsPaneCallback = this.props.printBundle.detailsCallback;
            cb.setPropValue(prop.name, e.currentTarget.value);
        }
    }

}

///////////////////////////////////////////////////////////////////////////////
// Misc
///////////////////////////////////////////////////////////////////////////////
class CvPrintUtil  {
    static applyColorStyle(c:Color, target?:any):any {
        let answer = ObjUtil.addAllProps(target, {});  // Do not mutate target
        if (c) {
            answer["color"] = ColorUtil.toColor(c);
        }
        return answer;
    }
    static applyFontSizeStyle(size:number, lineCount:number, target?:any) {
        let answer = ObjUtil.addAllProps(target, {});  // Do not mutate target
        if (!isNaN(size)) {
            // if (lineCount > 1) {
            answer["fontSize"] = size / lineCount * .70 + "px";
            // } else {
            //     answer["fontSize"] = size + "px";
            // }
        }
        return answer;
    }
    static applyFontStyle(fs:{bold?:boolean,underline?:boolean,italic?:boolean}, target?:any):any {
        let answer = ObjUtil.addAllProps(target, {});  // Do not mutate target
        if (fs.underline) {
            answer["textDecoration"] = "underline";
        }
        if (fs.bold) {
            answer["fontWeight"] = "bold";
        }
        if (fs.italic) {
            answer["fontStyle"] = "italic";
        }

        return answer;
    }

    static applyPaddingStyle(p:Edges, target?:any):any {
        // Not sure this is ever right.  Should we always use applyPaddingStyleToSizeAndLoc
        let answer = ObjUtil.addAllProps(target, {});  // Do not mutate target
        if (p) {
            answer["paddingTop"] = p.top;
            answer["paddingRight"] = p.right;
            answer["paddingBottom"] = p.bottom;
            answer["paddingLeft"] = p.left;
        }
        return answer;
    }
    static applyPaddingStyleToSizeAndLoc(p:Edges, target:any):any {
        let answer = ObjUtil.addAllProps(target, {});  // Do not mutate target
        if (p) {
            let h:number = answer["height"];
            let w:number = answer["width"]
            let left:number = answer["left"]
            let top:number = answer["top"]
            answer["height"] = Math.max(0, h - p.top - p.bottom);
            answer["width"] = Math.max(0, w - p.left - p.right);
            answer["top"] = top + p.top;
            answer["left"] = left + p.left;
        }
        return answer;
    }
    static applySizeAndLocationStyle(pp:PositionProps, target?:any):any {
        let answer = ObjUtil.addAllProps(target, {});  // Do not mutate target
        return ObjUtil.addAllProps(answer, {height:pp.height, width:pp.width, position:"absolute", left:pp.left, top:pp.top });
    }
    static applyTextAlignment(t:{textAlignment?:TextAlignment}, target?:any):any {
        let answer = ObjUtil.addAllProps(target, {});  // Do not mutate target
        if (t.textAlignment) {
            if (t.textAlignment.isRight) {
                answer["textAlign"] = "right"
                // answer["justifyContent"] = "flex-end"
            } else if (t.textAlignment.isCenter) {
                answer["textAlign"] = "center"
                // answer["justifyContent"] = "center"
            } else  {
                answer["textAlign"] = "left"
                // answer["justifyContent"] = "flex-start"
            }
        }
        return answer;
    }
    static applyTextAttributes(textSize:number, textAttributes:TextAttributes, target?:any):any {
        let answer = ObjUtil.addAllProps(target, {});  // Do not mutate target
        answer = CvPrintUtil.applyColorStyle(textAttributes.textColor, answer);
        answer = CvPrintUtil.applyTextAlignment(textAttributes, answer);
        answer = CvPrintUtil.applyFontSizeStyle(textSize, textAttributes.numberOfLines, answer);
        answer = CvPrintUtil.applyFontStyle(textAttributes, answer);
        if (textAttributes.numberOfLines > 1) {
            answer["whiteSpace"] = "pre-wrap";
        }
        return answer;
    }
    static buildImageProducers(readOnly:boolean):Array<CvImageProducer> {
        let imageProducers:Array<CvImageProducer> = null;
        if (!readOnly) {
            imageProducers = [
                {
                    // Pick a new file with the file chooser.
                    prodImageCB: (listener: CvValueListener<Object>)=> { listener(new CvImagePackagePick()) },
                    buttonClassName: CvImageDefaultAddButtonClass, includeWhenImageisPresent: true, includeWhenImageIsMissing: true,
                    includeWhenImageIsDeleted: true
                },
                {
                    // Delete existing image
                    prodImageCB: (listener: CvValueListener<Object>)=> { listener(new CvImagePackageDelete()) },
                    buttonClassName: CvImageDefaultRemoveButtonClass, includeWhenImageisPresent: true, includeWhenImageIsMissing: false,
                    includeWhenImageIsDeleted: false
                },
                {
                    // Undo previous delete image
                    prodImageCB: (listener: CvValueListener<Object>)=> { listener(new CvImagePackageUndo()) },
                    buttonClassName: CvImageDefaultUndoButtonClass, includeWhenImageisPresent: false, includeWhenImageIsMissing: false,
                    includeWhenImageIsDeleted: true
                }
            ];

        }
        return imageProducers;
    }
    static componentsAsChildren(printBundle:PrintBundle, components:Array<PTComponent>, fillWidth:number, fillHeight:number) {
        let answer:Array<any>=[];
        components.map((c, index) => {
            let h = c.layout.singleHeightRichNum().resolveWithFill(fillHeight);
            let w = c.layout.singleWidthRichNum().resolveWithFill(fillWidth);
            const b: Binding = (c as PTComponent).binding;
            if (b && b.propertyName) {
                var acd: AttributeCellValueDef = printBundle.printMarkupDef.orderedCellValueDefs[b.propertyName];
            }

            const pp:PositionProps = {height:h, width:w, left:c.layout.x, top:c.layout.y};
            if (c instanceof Label) {
                answer.push(<PLabel label={c} key={c.id} printBundle={printBundle} positionProps={pp}/>);
            } else if (c instanceof Checkbox && b) {
                answer.push(
                    <PCheckbox checkbox={c} key={c.id} printBundle={printBundle} positionProps={pp}/>
                );
            } else if (c instanceof DatePicker && b) {
                answer.push(
                    <PDatePicker datePicker={c} key={c.id} printBundle={printBundle} positionProps={pp}/>
                );
            } else if (c instanceof TimePicker && b) {
                answer.push(
                    <PTimePicker timePicker={c} key={c.id} printBundle={printBundle} positionProps={pp}/>
                );
            } else if (c instanceof SignatureCapture && b) {
                answer.push(
                    <PSignatureCapture signature={c} attributeCellValueDef={acd} key={c.id} printBundle={printBundle} positionProps={pp} />
                );
            } else {
                if (c instanceof PTComponent) {
                    if (b && b.propertyName) {
                        const detailsContext = printBundle.printMarkupContext;
                        const detailsCallback= printBundle.detailsCallback;
                        let style = CvPrintUtil.applySizeAndLocationStyle(pp, {});
                        let readOnly = false;

                        // Textish & TextAttributes
                        if (c.textAttributes) {
                            style = CvPrintUtil.applyTextAttributes(c.layout.singleHeightRichNum().resolveWithFill(fillHeight), c.textAttributes, style);
                        }
                        style = CvPrintUtil.applyPaddingStyle(c.padding, style);
                        if (c instanceof PTImage) {
                            // If this is an image, move the width & height to maxWidth & maxHeight and set the width & height
                            // to auto.  This should size and center the image in the space but preserve aspect ratio.
                            style = ObjUtil.addAllProps({objectFit:"contain"}, style);
                        }
                        let imageProducers:Array<CvImageProducer> = null;
                        if (c instanceof SignatureCapture) {
                            // Add signature capture here
                        } else if (c instanceof PTImage) {
                            readOnly = !(c as PTImage).allowPickOptions;
                            imageProducers = this.buildImageProducers(readOnly);
                        }

                        const emptyClassSelector = (propDef:PropDef, defaultClass?:string):string => { return "cv-print-control" }
                        // const emptyClassSelector = (propDef:PropDef, defaultClass?:string):string => { return "" }
                        const hasFocus = hasInitialFocus(printBundle.printMarkupContext, b.propertyName);

                        answer.push(
                            <CvAttributeCellValueDef detailsContext={detailsContext} detailsCallback={detailsCallback}
                                                     classSelector={emptyClassSelector} cellValueDef={acd} key={index + '_' + index}
                                                     imageProducers={imageProducers} inlineStyle={style} imageReadOnly={readOnly} hasFocus={hasFocus}/>
                        );
                    } else {
                        // Components with no property binding
                        if (c instanceof Button) {
                            answer.push(
                                <PButton button={c} key={c.id} printBundle={printBundle} positionProps={pp}/>
                            );
                        } else if (c instanceof PTImage) {
                            answer.push(
                                <PImage image={c} key={c.id} printBundle={printBundle} positionProps={pp}/>
                            );
                        }
                    }
                }
            }
        });
        return answer;
    }
    static gridsAsChildren(printBundle:PrintBundle, grids:Array<Grid>, parentSize:number) {
        let answer:Array<any>=[];
        grids.map(function(g) {
            let h=g.layout.sumOfHeights();
            let w=g.layout.sumOfWidths(parentSize);

            // Add the Grid containing the cells
            const pp:PositionProps = {height:h, width:w, left:g.layout.x, top:g.layout.y};
            answer.push(<PGrid key={g.id} printBundle={printBundle} grid={g} positionProps={pp} parentSize={parentSize} />);

            // Add the grid lines element (draws the grid lines on a canvas)
            answer.push(<PGridLines key={"GL_gen_" + g.id} printBundle={printBundle} grid={g} positionProps={pp}  />);
        });
        return answer;
    }
}

///////////////////////////////////////////////////////////////////////////////
// CvPrintPanel
///////////////////////////////////////////////////////////////////////////////

/**
 * Renders a Details Panel, styled by CSS
 */
export let CvPrintPanel = React.createClass<CvPrintPanelProps, CvPrintPanelState>({

    mixins: [CvBaseMixin],

    render: function () {

        return  <CvDetailsPane {...this.props} detailsContext={this.props.printMarkupContext} detailsRenderer={(cvContext:CvContext,
                             record:EntityRec, detailsCallback:CvDetailsPaneCallback)=>{

                             const printMarkupContext:PrintMarkupContext = cvContext.scopeCtx.scopeObj;
                             const printMarkupDef:PrintMarkupDef = printMarkupContext.printMarkupDef;
                             const printMarkupModel:Form=printMarkupDef.printMarkupModel;
                             const listeners = {navigationListeners:this.props.navigationListeners, stateChangeListeners:this.props.stateChangeListeners,
                                                actionListeners:this.props.actionListeners };
                             const printBundle = new PrintBundle(printMarkupDef, printMarkupContext, detailsCallback, listeners);

                             return (
                                <CvRecord entityRec={record} renderer={(cvContext:CvContext)=>{
                                   return (
                                            <div className="cv-component-container cv-detail-container"  >
                                                {(()=>{
                                                    if(printMarkupContext.printMarkupDef.editable) {
                                                        if(printMarkupContext.isReadMode) {
                                                            return <div className="cv-component-toolbar">
                                                                <CvDetailsPanelControls onRefresh={this._refresh.bind(this, detailsCallback)}
                                                                onEdit={this._openWriteMode.bind(this, detailsCallback)}/>
                                                            </div>
                                                        } else if (printMarkupContext.isWriteMode){
                                                            const cancelButtonText = printMarkupContext.printMarkupDef.cancelButtonText;
                                                            const commitButtonText = printMarkupContext.printMarkupDef.commitButtonText;
                                                             return <div className="cv-component-toolbar">
                                                                 <CvDetailsPanelCommitCancelControls cancelButtonText={cancelButtonText} commitButtonText={commitButtonText}
                                                                onCancel={this._openReadMode.bind(this, detailsCallback)} onCommit={this._save.bind(this, detailsCallback)}/>
                                                             </div>
                                                        }
                                                    } else {
                                                        return null
                                                    }
                                                })()}
                                                <div className="cv-component-body"><div className="cv-component-scroll">
                                                    <PForm printBundle={printBundle}/>
                                                </div></div>
                                                {(()=>{
                                                    const cancelButtonText = printMarkupContext.printMarkupDef.cancelButtonText;
                                                    const commitButtonText = printMarkupContext.printMarkupDef.commitButtonText;
                                                    return (printMarkupContext.printMarkupDef.editable && printMarkupContext.isWriteMode) ?
                                                        <div className="cv-component-toolbar">
                                                            <CvDetailsPanelCommitCancelControls cancelButtonText={cancelButtonText} commitButtonText={commitButtonText}
                                                                                                onCancel={this._openReadMode.bind(this, detailsCallback)} onCommit={this._save.bind(this, detailsCallback)}/></div> : null;
                                                })()}

                                            </div>
                                    );
                                }}/>
                            );

               }}/>
    },

    // componentDidMount() {
    //     this.setState({
    //         styles: {
    //             top: computeTopWith(this.refs.child),
    //             left: computeLeftWith(this.refs.child)
    //         }
    //     })
    // },

    _isSectionTitleDef: function (row) {
        return row.every((cellDef:CellDef)=>{ return cellDef.values.every((cellValueDef:CellValueDef)=>{
            return cellValueDef instanceof LabelCellValueDef;
        })});
    },

    _cellCount(rows:Array<Array<CellDef>>):number{
        return rows.reduce((prev, cellDefRow:Array<CellDef>)=>{
            const numCellValueDefs:number = cellDefRow.reduce((prev, cellDef:CellDef)=>{
                return cellDef.values ? prev + cellDef.values.length : prev;
            }, 0);
            return numCellValueDefs > prev ?  numCellValueDefs : prev;
        }, 0);
    },

    _openWriteMode: function(detailsCallback:CvDetailsPaneCallback) {
        detailsCallback.openWriteMode((success, error)=>{
        });
    },

    _openReadMode: function(detailsCallback:CvDetailsPaneCallback) {
        detailsCallback.openReadMode((success, error)=>{
        });
    },

    _refresh: function(detailsCallback:CvDetailsPaneCallback) {
        detailsCallback.refresh();
    },

    _save: function(detailsCallback:CvDetailsPaneCallback) {
        detailsCallback.saveChanges((success, error)=>{
        });
    }


});