//
//  SVPlugIn.h
//  Sandvox
//
//  Created by Mike on 29/12/2009.
//  Copyright 2009-2012 Karelia Software. All rights reserved.
//
//  THIS SOFTWARE IS PROVIDED BY KARELIA SOFTWARE AND ITS CONTRIBUTORS "AS-IS"
//  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
//  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
//  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
//  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
//  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
//  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
//  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
//  ARISING IN ANY WAY OUR OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
//  POSSIBILITY OF SUCH DAMAGE.
//


//  This header should be well commented as to its functionality. Further information can be found at 
//  http://www.karelia.com/sandvox/help/z/Sandvox_Developers_Guide.html


#import <Cocoa/Cocoa.h>


extern NSString * const SVDestinationResourcesDirectory;    // upload *into* the _Resources folder
extern NSString * const SVDestinationDesignDirectory;       // upload *into* the design's folder
extern NSString * const SVDestinationMainCSS;               // *prepend* to design's main.css


typedef NS_OPTIONS(NSUInteger, SVResizingOptions) {
    SVResizingDisableVertically = 1 << 6,   // disable/remove the vertical resize handles
};


// Sandvox 2.8 generates high-resolution images when possible if -generatesHighResolutionImages is set to YES
typedef NS_OPTIONS(NSUInteger, SVPageImageRepresentationOptions) {
    SVImageScaleAspectFit = 1 << 0, // without this, image will be cropped to fill width & height
    SVImageScaleUpAvoid = 1 << 1 AVAILABLE_SANDVOX_VERSION_2_2_AND_LATER,  // stops the image being scaled up
};


typedef NS_ENUM(NSUInteger, SVCodeInjectionPoint) {
    SVCodeInjectionPointBeforeHTML = 5,
    SVCodeInjectionPointHeader = 25,
    SVCodeInjectionPointStartOfBody = 65,
    SVCodeInjectionPointEndOfBody = 195,
} AVAILABLE_SANDVOX_VERSION_2_5_AND_LATER;



#pragma mark -


@class SVPlugIn, SVInspectorViewController;
@protocol SVPage;


#pragma mark -


@protocol SVPlugInContext

#pragma mark Purpose
- (BOOL)isForEditing; // YES if the HTML is to appear in Sandvox's web view
- (BOOL)isForQuickLookPreview;  // yeah, you get the idea
- (BOOL)liveDataFeeds;  // When NO, you should write placeholders instead of loading from the web
- (NSString *)language; // potentially nil, but rare. Better to use than [[context page] language]


#pragma mark State
- (BOOL)isWritingPagelet;   // YES if currently writing a plug-in for the sidebar or callout


#pragma mark Enumerating With CSS Class Name Generation
// Arrays can be enumerated by calling this method or from within a template. While enumerating, you can ask the context what class name is appropriate for including with an element. e.g.
//      class="i3 o last"
// Designs and plug-ins can use these class names to style specific elements without needing CSS3 browser support.
// You can also ask the context for the currently object
- (void)enumerateObjectsInArray:(NSArray *)array usingBlock:(void (^)(id obj, NSUInteger idx))block AVAILABLE_SANDVOX_VERSION_2_7_AND_LATER;
- (NSString *)currentIterationCSSClassNameIncludingArticle:(BOOL)includeArticle;
- (id)objectForCurrentTemplateIteration;


#pragma mark URLs

// To make markup more flexible, relative string should generally be used instead of full URLs. This method quickly generates the best way to get from the current page to a given URL.
- (NSString *)relativeStringFromURL:(NSURL *)URL;

// Where the HTML being generated is destined for. nil if unknown/irrelevant. You should generally have no need to use this API as -relativeStringFromURL: gives better-informed results (i.e. relative when possible)
@property(nonatomic, copy, readonly) NSURL *baseURL;

// Returns baseName.js or baseName-vers.js
// but if jQueryDebug user default is set, then name is like baseName.min.js or baseName-vers.min.js
// Use this if you have both a regular and a minimized script, so it's easy to debug the scripts.
- (NSString *)filenameForScriptBase:(NSString *)baseName version:(NSString *)optionalVersion AVAILABLE_SANDVOX_VERSION_2_8_AND_LATER __attribute__((nonnull(1)));


#pragma mark Basic HTML Writing

/*  SVPlugInContext is heavily based on Karelia's open source KSHTMLWriter class. You can generate HTML by writing a series of elements, text and comments. For more information https://github.com/karelia/KSHTMLWriter should prove helpful.
 *  I've documented the equivalent markup each method produces below
 *  For attribute dictionaries, we recommend you use the SVATTR() macro. 
 *
 *  Note that in some Sandvox releases, SVPlugInContext is implemented using KSHTMLWriter, but do NOT attempt to use any methods not listed in this header as it may well change in the future.
 */

- (void)writeElement:(NSString *)name attributes:(NSDictionary *)attributes content:(void (^)(void))content AVAILABLE_SANDVOX_VERSION_2_5_AND_LATER __attribute__((nonnull(1)));

// Use this for making attribute dictionary for passing to -writeElement:attributes:content:
// It is less verbose than using NSDictionary directly, but also smart enough to maintain the ordering of the attributes you supply it
#define SVATTR(...) [NSDictionary svDictionaryWithAttributesAndValues:__VA_ARGS__, nil]

// Convenience for if you don't want any attributes
- (void)writeElement:(NSString *)name content:(void (^)(void))content AVAILABLE_SANDVOX_VERSION_2_5_AND_LATER __attribute__((nonnull(1)));

// Escapes the string. Nil strings are fine as of 2.2.3; they just have no effect!
- (void)writeCharacters:(NSString *)string;

// Escapes the string, and wraps in a comment tag. Must not be nil
- (void)writeComment:(NSString *)comment __attribute__((nonnull(1)));   // <!--comment-->

// Great for when you have an existing snippet of HTML
- (void)writeHTMLString:(NSString *)html __attribute__((nonnull(1)));


#pragma mark Unique IDs
// For when you need to guarantee the element's ID is unique within the document. Perfect for hooking up CSS and scripts. Returns the best unique ID available
// Note that the id is NOT registered as in-use until you actually write an element using it
// If the ID is already taken, a new one is returned by appending 2 (or higher as needed) to the input. Prior to Sandvox 2.6.6, a hyphen was included too which made the result unsuitable for javascript function *names*. Can work around by replacing them with something suitable like an underscore
// At present, ids containing characters unsuitable for HTML are left intact, but we reserve the right to change that in future
// You can tell if an id is already in use by seeing if the result of this method matches the input
- (NSString *)generateUniqueElementID:(NSString *)preferredID AVAILABLE_SANDVOX_VERSION_2_5_AND_LATER __attribute__((nonnull(1)));


#pragma mark Hyperlinks
// content block must be non-nil for these methods

//  <a href="..." target="..." rel="...">...</a>
- (void)writeLinkToHref:(NSString *)href
                  title:(NSString *)titleString
                 target:(NSString *)targetString
                    rel:(NSString *)relString
                content:(void (^)(void))content AVAILABLE_SANDVOX_VERSION_2_5_AND_LATER __attribute__((nonnull(5)));

// Takes care of using the right href, title and target for the page
- (void)writeLinkToPage:(id <SVPage>)page
             attributes:(NSDictionary *)attributes
                content:(void (^)(void))content AVAILABLE_SANDVOX_VERSION_2_5_AND_LATER __attribute__((nonnull(1,3)));

// Adds dependency on the page's feed for you. If the page doesn't generate a feed, context will take care of writing out a placeholder, rather than running your content block
- (void)writeLinkToFeedForPage:(id <SVPage>)page
                    attributes:(NSDictionary *)attributes
                       content:(void (^)(void))content AVAILABLE_SANDVOX_VERSION_2_5_AND_LATER __attribute__((nonnull(1,3)));


#pragma mark Headings
//  <hX>
// The context will know what is the appropriate level of header to write. E.g. in a pagelet <H5>s are wanted, but for inline graphics use <H3>
// Returns the level that was written. e.g. <H4> would return 4
// Empty headings don't generally make sense, so content block can't be nil
- (NSUInteger)writeHeadingWithAttributes:(NSDictionary *)attributes content:(void (^)(void))content AVAILABLE_SANDVOX_VERSION_2_5_AND_LATER __attribute__((nonnull(2)));


#pragma mark CSS and other Resources

// These methods return the URL to use for the resource in relation to this context. You can then pass it to -relativeStringFromURL: for example
// You almost always want to use one of the string constants declared above for the destination. If more control is needed, you are welcome to append path components to a directory constant to specify the exact path
// nil will be returned if there is a problem with the provided data. As of 2.5.5, Sandvox tries to do that when adding two different pieces of data to the same destination, since only the first one can "win"

// If fileURL refers to a directory, @2x files are automatically excluded when .generatesHighResolutionImages is turned off
- (NSURL *)addResourceAtURL:(NSURL *)fileURL
                destination:(NSString *)uploadPath
                    options:(NSUInteger)options /* pass in 0 */ __attribute__((nonnull(1,2)));

// Like the above, but for in-memory data. You should append your preferred filename to one of the destination constants, otherwise Sandvox will be forced to invent a filename
- (NSURL *)addResourceWithData:(NSData *)data
                      MIMEType:(NSString *)type     // use UTTypeCopyPreferredTagWithClass() to convert from UTI if needed
              textEncodingName:(NSString *)encoding // IANA encoding name (e.g. “utf-8” or “utf-16”). nil for non-text files
                   destination:(NSString *)uploadPath
                       options:(NSUInteger)options /* pass in 0 */   __attribute__((nonnull(1,4)));


/*  All these methods return the URL of the file where the CSS will end up. Presently while editing this is nil
 */

// Referencing images and other resources from the CSS is inadvisable since its location depends on the context's purpose
// If css is nil, just returns the location of the CSS file
- (NSURL *)addCSSString:(NSString *)css;

// For CSS that refers to other files, the context must be asked where those files are. You can do this by hand, building up a string and passing to -addCSSString: or there's this method. The CSS will be parsed just like Template.html. Generally, your plug-in is the object to be parsed
- (NSURL *)addCSSWithTemplateAtURL:(NSURL *)templateURL object:(id)object __attribute__((nonnull(1)));


#pragma mark Metrics
// The element's size will be taken from plug-in's .width and .height properties. When editing, that will be kept up-to-date, with resize handles if appropriate
- (void)writeResizableElement:(NSString *)elementName
                       plugIn:(SVPlugIn *)plugIn
                      options:(SVResizingOptions)options
                   attributes:(NSDictionary *)attributes
                      content:(void (^)(void))content AVAILABLE_SANDVOX_VERSION_2_5_AND_LATER __attribute__((nonnull(1,2)));


#pragma mark Page Titles

/**
 It's possible for people to apply custom formatting to all or part of a page title using the Format
 menu. If so, that generates some extra HTML tags _inside_ of the page title. Pass `YES` for 
 `plainText` to have those tags stripped out.
 
 @param elementName Must not be nil. A <SPAN> is often a good choice
 @param plainText Whether to strip out any custom formatting. See discussion above for more detail.
 */
- (void)writeElement:(NSString *)elementName
     withTitleOfPage:(id <SVPage>)page
         asPlainText:(BOOL)plainText
          attributes:(NSDictionary *)attributes __attribute__((nonnull(1,2)));


#pragma mark Images

/*  All image representation methods automatically register dependencies internally should the user change the image in use
 */

// Sizes passed to this method are in *points*. When -generatesHighResolutionImages is YES, writes out an <img> tag at the specified size, but with an underlying bitmap of up to twice that
// Call -pageHasImageRepresentation: first if you want to know if there is a decent thumbnail before proceeding to write
- (void)writeImageRepresentationOfPage:(id <SVPage>)page  // nil page will write a placeholder image
                                 width:(NSUInteger)width
                                height:(NSUInteger)height
                            attributes:(NSDictionary *)attributes  // e.g. custom CSS class
                               options:(SVPageImageRepresentationOptions)options;

- (BOOL)pageHasImageRepresentation:(id <SVPage>)page AVAILABLE_SANDVOX_VERSION_2_8_AND_LATER;

/**
 Pass in 0 width and height to get the largest image available. If you do this, `options` becomes
 irrelevant, since there's no point cropping or scaling the largest image!
 
 Note: for some time prior to Sandvox 2.9.4, 0 sizes were incorrectly handled and threw an exception
 instead. As a workaround, you can pass as large a size as you might ever need, and the options
 `SVImageScaleAspectFit|SVImageScaleUpAvoid` to bring back down to correct size.
 
 `nil` may be returned if:
 * the page has no thumbnail
 * that thumbnail fails to be read from disk
 * the publishing system doesn't yet know where to place the image, but will swing back round to generating the page once it does
 
 Sizes passed to this method are in *pixels*, so adjust based on `.generatesHighResolutionImages` if needed
 */
- (NSURL *)URLForImageRepresentationOfPage:(id <SVPage>)page
                                     width:(NSUInteger)width
                                    height:(NSUInteger)height
                                   options:(SVPageImageRepresentationOptions)options;

- (void)writeImageWithSrc:(NSString *)src
        highResolutionSrc:(NSString *)retinaSrc
                      alt:(NSString *)alt
                    width:(id)width
                   height:(id)height
               attributes:(NSDictionary *)attributes AVAILABLE_SANDVOX_VERSION_2_8_AND_LATER;    // e.g. custom CSS class

// Whether retina-resolution (2x) images should be generated
@property(nonatomic, readonly) BOOL generatesHighResolutionImages AVAILABLE_SANDVOX_VERSION_2_8_AND_LATER;


#pragma mark Javascript and other Code Injection

// In general, user-supplied Code Injection takes priority over your plug-in (i.e. it appears first)
// If the exact same code has already been injected, it is not included again
// As of Sandvox 2.8, nil markup is ignored. In earlier releases it tended to throw an exception
- (void)injectCode:(NSString *)markup atPoint:(SVCodeInjectionPoint)injectionPoint AVAILABLE_SANDVOX_VERSION_2_5_AND_LATER;

- (void)injectJavascriptWithSrc:(NSString *)path attributes:(NSDictionary *)attributes AVAILABLE_SANDVOX_VERSION_2_8_AND_LATER __attribute__((nonnull(1)));
- (void)injectJavascript:(NSString *)markup AVAILABLE_SANDVOX_VERSION_2_8_AND_LATER __attribute__((nonnull(1)));

// The script is uploaded to _Resources, keeping the same filename
- (void)addJavascriptResourceWithTemplateAtURL:(NSURL *)templateURL
                                        object:(id)object __attribute__((nonnull(1)));

#pragma mark Dependencies
// When generating HTML using a template, Sandvox automatically registers each keypath it encounters in the template as a dependency. If you need to register any additonal paths — perhaps because you are not using a template or it doesn't appear in the template — do so with this method.
// When a change of the path is detected, Sandvox will take care of reloading the needed bit of the webview.
- (void)addDependencyForKeyPath:(NSString *)keyPath ofObject:(NSObject *)object __attribute__((nonnull(1,2)));


#pragma mark Pages

// Returns the page that this context is being used to generate. Note that Sandvox sometimes needs to write HTML for reasons not specific to a particular page, and so this might return nil
- (id <SVPage>)page;

- (NSURL *)URLForPage:(id <SVPage>)page;    // page may be nil (returns nil)

// These methods fetch the children of a page, registering required dependencies for you, and applying filter as requested
- (NSArray *)childrenOfPage:(id <SVPage>)page           AVAILABLE_SANDVOX_VERSION_2_2_AND_LATER __attribute__((nonnull(1)));  
- (NSArray *)indexChildrenOfPage:(id <SVPage>)page      AVAILABLE_SANDVOX_VERSION_2_2_AND_LATER __attribute__((nonnull(1)));
- (NSArray *)sitemapChildrenOfPage:(id <SVPage>)page    AVAILABLE_SANDVOX_VERSION_2_2_AND_LATER __attribute__((nonnull(1)));


#pragma mark - Deprecated

// Each call to start an element should be balanced with a later call to end it. Even better is if you can use the new 2.5+ block-based APIs instead!
- (void)startElement:(NSString *)elementName attributes:(NSDictionary *)attributes DEPRECATED_IN_SANDVOX_VERSION_2_5_AND_LATER("Use -writeElement:attributes:contents: instead"); // <tag>
- (void)endElement DEPRECATED_IN_SANDVOX_VERSION_2_5_AND_LATER("Use -writeElement:attributes:contents: instead");                                                                 // </tag>

- (void)startElement:(NSString *)elementName DEPRECATED_IN_SANDVOX_VERSION_2_5_AND_LATER("Use -writeElement:contents: instead");
- (void)startElement:(NSString *)elementName className:(NSString *)className DEPRECATED_IN_SANDVOX_VERSION_2_5_AND_LATER("Use -writeElement:attributes:contents: instead");
- (void)startElement:(NSString *)elementName idName:(NSString *)idName className:(NSString *)className DEPRECATED_IN_SANDVOX_VERSION_2_5_AND_LATER("Use -writeElement:attributes:contents: instead");


- (NSString *)startElement:(NSString *)elementName
           preferredIdName:(NSString *)preferredID
                 className:(NSString *)className
                attributes:(NSDictionary *)attributes DEPRECATED_IN_SANDVOX_VERSION_2_5_AND_LATER("Use -writeElement:attributes:contents: instead");

- (void)startAnchorElementWithHref:(NSString *)href title:(NSString *)titleString target:(NSString *)targetString rel:(NSString *)relString DEPRECATED_IN_SANDVOX_VERSION_2_5_AND_LATER("Use -writeLinkToHref:title:target:rel:content: instead");
- (void)startAnchorElementWithPage:(id <SVPage>)page attributes:(NSDictionary *)attributes DEPRECATED_IN_SANDVOX_VERSION_2_5_AND_LATER("Use -writeLinkToPage:attributes:contents: instead") AVAILABLE_SANDVOX_VERSION_2_2_AND_LATER;
- (void)startAnchorElementWithPage:(id <SVPage>)page DEPRECATED_IN_SANDVOX_VERSION_2_5_AND_LATER("Use -writeLinkToPage:attributes:contents: instead");

// Returns YES to signify success.
// Returns NO if the page has no RSS feed, so cannot be linked to. The context will write a placeholder instead if appropriate; you should *not* write any content of your own or call -endElement; just move on!
- (BOOL)startAnchorElementWithFeedForPage:(id <SVPage>)page attributes:(NSDictionary *)attributes DEPRECATED_IN_SANDVOX_VERSION_2_5_AND_LATER("Use -writeLinkToFeedForPage:attributes:contents: instead") AVAILABLE_SANDVOX_VERSION_2_2_AND_LATER;

- (NSUInteger)startHeadingWithAttributes:(NSDictionary *)attributes DEPRECATED_IN_SANDVOX_VERSION_2_5_AND_LATER("Use -writeWithAttributes:contents: instead");

- (NSString *)startResizableElement:(NSString *)elementName
                             plugIn:(SVPlugIn *)plugIn
                            options:(SVResizingOptions)options
                    preferredIdName:(NSString *)preferredID
                         attributes:(NSDictionary *)attributes DEPRECATED_IN_SANDVOX_VERSION_2_5_AND_LATER("Use -writeResizableElement:plugIn:options:attributes:contents: instead");

// Superseded by -injectCode:atPoint:, Corresponds to the SVCodeInjectionPointBeforeHTML, SVCodeInjectionPointHeader and SVCodeInjectionPointEndOfBody constants
- (void)addMarkupBeforeHTML:(NSString *)markup DEPRECATED_IN_SANDVOX_VERSION_2_5_AND_LATER("Use -injectCode:atPoint:SVCodeInjectionPointBeforeHTML instead") AVAILABLE_SANDVOX_VERSION_2_2_AND_LATER;
- (void)addMarkupToHead:(NSString *)markup DEPRECATED_IN_SANDVOX_VERSION_2_5_AND_LATER("Use -injectCode:atPoint:SVCodeInjectionPointHeader instead") AVAILABLE_SANDVOX_VERSION_2_1_AND_LATER;
- (void)addMarkupToEndOfBody:(NSString *)markup DEPRECATED_IN_SANDVOX_VERSION_2_5_AND_LATER("Use -injectCode:atPoint:SVCodeInjectionPointEndOfBody instead");

// Including scripts in the middle of some HTML is generally bad for performance; you likely want to move to one of the -injectJavascript… methods
- (void)writeJavascriptWithSrc:(NSString *)src encoding:(NSStringEncoding)encoding DEPRECATED_IN_SANDVOX_VERSION_2_8_AND_LATER("Use -injectJavascriptWithSrc: or -writeElement: instead");;

@end


#pragma mark -


@interface NSDictionary (SVPlugInContext)
// You probably want to use SVATTR() rather than this method directly
+ (NSDictionary *)svDictionaryWithAttributesAndValues:(NSString *)anAttribute, ... NS_REQUIRES_NIL_TERMINATION;
@end

